Nginx Web Server
This guide was written for Ubuntu Server 26.04 LTS (Noble Numbat), Nginx 1.28.3, OpenSSL 3.5.5, and Certbot 5.6.0.
Nginx
Nginx (pronounced “engine x”) is the world’s most popular web server, primarily used for serving static files or acting as a reverse proxy.
Installation
Check for package updates and install nginx:
sudo apt update && sudo apt install nginxEnable a site
-
Create a new configuration file for your site, replacing
example.comwith your domain name:Terminal window sudo nano /etc/nginx/sites-available/example.com -
Copy and paste the following configuration into the file, replacing
example.comwith your domain name:/etc/nginx/sites-available/example.com server {listen 80;listen [::]:80;server_name example.com;root /var/www/example.com;index index.html;location / {try_files $uri $uri/ =404;}}Press Ctrl + O to save the file and Ctrl + X to exit.
-
Enable a site by creating a symbolic link from
sites-availabletosites-enabled, replacingexample.comwith your domain name:Terminal window sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/ -
Check the configuration for syntax errors:
Terminal window sudo nginx -t -
Reload
nginxto apply the changes:Terminal window sudo systemctl reload nginx -
Check the status of
nginxto ensure it’s running:Terminal window sudo systemctl status nginx -
Open your web browser and visit
http://example.comto confirm that the site is working.
Allow traffic through the firewall
-
List available application profiles:
Terminal window sudo ufw app list -
Allow
nginxthrough the firewall:nginxcomes installed with pre-configured UFW application profiles that allow traffic on port 80 (Nginx HTTP), port 443 (Nginx HTTPS), or both (Nginx Full).Replace
Nginx FullwithNginx HTTPorNginx HTTPSas needed:Terminal window sudo ufw allow "Nginx Full"
-
Optional: Limit access to specific IP addresses:
Terminal window sudo ufw allow from 192.0.2.1 to any app "Nginx Full" comment "Allow from specific IP"See Cloudflare for a specific use case.
Certbot
Certbot is a free, open-source command line tool that automates the process of obtaining, installing, and renewing SSL/TLS certificates from Let’s Encrypt to enable secure HTTPS connections.
Installation
-
Install Certbot:
Terminal window sudo snap install --classic certbot -
Enable the
certbotcommand:Terminal window sudo ln -s /snap/bin/certbot /usr/bin/certbot -
Confirm the plugin containment level:
Terminal window sudo snap set certbot trust-plugin-with-root=ok -
Install the appropriate DNS plugin, replacing
cloudflarewith the name of your DNS provider:Terminal window sudo snap install certbot-dns-cloudflare
Configuration
You’ll need to set up DNS credentials.
Follow the steps for the previously installed DNS plugin.
For example, if your DNS provider is Cloudflare, you’d run the following commands:
-
Create a credentials file:
Terminal window mkdir -p ~/.secrets/certbottouch ~/.secrets/certbot/cloudflare.ini -
Open the file in a text editor:
Terminal window nano ~/.secrets/certbot/cloudflare.ini -
Copy and paste the following code into the text editor:
~/.secrets/certbot/cloudflare.ini # Cloudflare API token used by Certbotdns_cloudflare_api_token = 0123456789abcdef0123456789abcdef01234567 -
Visit the Cloudflare API Tokens page and create a new User API Token using the API token template
Edit zone DNS. Replace the example token with the generated token. If you accidentally leave the page, you’ll need to roll a new token. -
Press Ctrl + O to save the file and Ctrl + X to exit.
-
Secure the new directories and file:
Terminal window chmod 700 ~/.secrets ~/.secrets/certbotchmod 600 ~/.secrets/certbot/cloudflare.ini
Obtain a certificate
Run one of the following commands, replacing user with your Linux user and example.com with your domain name:
-
Domain and www subdomain
Domain and www subdomain certbot certonly \--dns-cloudflare \--dns-cloudflare-credentials /home/user/.secrets/certbot/cloudflare.ini \-d example.com \-d www.example.com -
Domain and all subdomains
Domain and all subdomains certbot certonly \--dns-cloudflare \--dns-cloudflare-credentials /home/user/.secrets/certbot/cloudflare.ini \-d example.com \-d *.example.com -
Subdomain only
Subdomain only certbot certonly \--dns-cloudflare \--dns-cloudflare-credentials /home/user/.secrets/certbot/cloudflare.ini \-d app.example.com
The official documentation gives ~/.secrets/certbot/cloudflare.ini as the example path to the credentials file, but I’ve found that leads to issues with automatic renewals.
Test automatic renewal
The Certbot packages on your system come with a cron job or systemd timer that will renew your certificates automatically before they expire. You will not need to run Certbot again, unless you change your configuration. You can test automatic renewal for your certificates by running this command:
sudo certbot renew --dry-runThe command to renew certbot is installed in one of the following locations:
/etc/crontab//etc/cron.*/*systemctl list-timers
Install the certificate
-
Open the configuration file for your site, replacing
example.comwith your domain name:Terminal window sudo nano /etc/nginx/sites-available/example.com -
Enable HTTPS by changing the
listenport to 443, addsslparameter, and include the SSL certificate paths. This is also a good opportunity to enable HTTP/2./etc/nginx/sites-available/example.com server {listen 80;listen [::]:80;listen 443 ssl;listen [::]:443 ssl;http2 on;server_name example.com;root /var/www/example.com;index index.html;location / {try_files $uri $uri/ =404;}# SSL certificatesssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;}Press Ctrl + O to save the file and Ctrl + X to exit.
-
Check the configuration and reload
nginxto apply the changes:Terminal window sudo nginx -t && sudo systemctl reload nginx
Confirm that Certbot worked
To confirm that your site is set up properly, visit https://example.com/ in your browser and look for the lock icon in the URL bar.
Harden SSL configuration
Global settings
The following settings will enhance SSL performance and protect Perfect Forward Secrecy (PFS).
-
Open the configuration file:
Terminal window sudo nano /etc/nginx/nginx.conf -
Add the following lines:
/etc/nginx/nginx.conf ### SSL Settings##ssl_protocols TLSv1.2 TLSv1.3; # Dropping SSLv3 (POODLE), TLS 1.0, 1.1ssl_prefer_server_ciphers off; # Don't force server cipher order.# TLS session resumption settings (works with TLS 1.2 and 1.3)ssl_session_cache shared:SSL:10m;ssl_session_timeout 1d;ssl_session_tickets off; -
Press Ctrl + O to save the file and Ctrl + X to exit.
-
Check the configuration and reload
nginxto apply the changes:Terminal window sudo nginx -t && sudo systemctl reload nginx
Per-site settings
Use the Mozilla SSL Configuration Generator to generate a strong SSL configuration for your server. The following example uses the “Modern” configuration for Nginx 1.28.3 and OpenSSL 3.5.5.
Use the “Intermediate” configuration if you are not running behind a Cloudflare proxy to make sure you support all clients. Do not disable TLS 1.2 on Cloudflare. Things will break like social media platforms or other apps previewing links to your websites.
-
Verify
nginxandopensslversions:Terminal window nginx -v; openssl version -
Open the configuration file for your site, replacing
example.comwith your domain name:Terminal window sudo nano /etc/nginx/sites-available/example.com -
Add the following lines to the server block, replacing
example.comwith your domain name:/etc/nginx/sites-available/example.com server {listen 443 ssl;listen [::]:443 ssl;http2 on;server_name example.com;root /var/www/example.com;index index.html;location / {try_files $uri $uri/ =404;}# SSL certificatesssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;# HSTS (ngx_http_headers_module is required) (63072000 seconds)add_header Strict-Transport-Security "max-age=63072000" always;# Modern configurationssl_protocols TLSv1.3;ssl_ecdh_curve X25519MLKEM768:X25519:prime256v1:secp384r1;ssl_prefer_server_ciphers off;}HSTS is a security feature that tells browsers to only connect to your site using HTTPS. Once you enable HSTS, you cannot disable it for 63072000 seconds (2 years). Make sure your site is fully functional over HTTPS before enabling HSTS.
-
Press Ctrl + O to save the file and Ctrl + X to exit.
-
Check the configuration and reload
nginxto apply the changes:Terminal window sudo nginx -t && sudo systemctl reload nginx
Cloudflare
Restrict HTTPS traffic through the firewall
If you’re using Cloudflare as a proxy server for your website, you can restrict access to your origin server to only allow requests originating from Cloudflare’s IP addresses.
Remove existing rules
If you have existing rules that allow traffic from all IP addresses, you may want to remove them before adding the new rules.
-
List the current UFW rules as a numbered list:
Terminal window sudo ufw status numbered -
Either delete a rule by the rule name or number:
-
Delete a rule by the name:
Terminal window sudo ufw delete allow "Nginx Full" -
Delete a rule by the number:
Terminal window sudo ufw delete NUMNote: The numbered list will change after a rule is added or deleted.
Manually allow Cloudflare IPs
Run the following command to allow traffic from Cloudflare’s IP addresses through the firewall. Replace 192.0.2.1 with each IP address listed on the Cloudflare IP Ranges page.
sudo ufw allow from 192.0.2.1 to any app "Nginx HTTPS" comment "Allow from specific IP"Using a script
You may automate the process of adding Cloudflare’s IP addresses to UFW by using a script. The following script fetches the latest IP addresses from Cloudflare’s API and adds them to UFW.
-
Create a file named
add_cloudflare_ufw_rules.sh:Terminal window nano add_cloudflare_ufw_rules.sh -
Copy and paste the following code into the file:
add_cloudflare_ufw_rules.sh #!/bin/bash# Generated by ChatGPT# Cloudflare API URLAPI_URL="https://api.cloudflare.com/client/v4/ips"# Function to fetch IPs from Cloudflare API and apply UFW rulesapply_ufw_rules() {echo "Fetching IP list from Cloudflare API..."# Fetch the JSON responseresponse=$(curl -s "$API_URL")# Extract IPv4 and IPv6 ranges using grep, sed, and awkipv4_list=$(echo "$response" | grep -o '"ipv4_cidrs":\[[^]]*' | sed 's/"ipv4_cidrs":\[//' | tr -d '"' | tr ',' '\n')ipv6_list=$(echo "$response" | grep -o '"ipv6_cidrs":\[[^]]*' | sed 's/"ipv6_cidrs":\[//' | tr -d '"' | tr ',' '\n')# Apply UFW rules for IPv4echo "Applying IPv4 rules..."for ip in $ipv4_list; doif [[ ! -z "$ip" ]]; thenecho "Adding UFW rule for $ip..."sudo ufw allow from "$ip" to any app "Nginx HTTPS" comment "Cloudflare"fidone# Apply UFW rules for IPv6echo "Applying IPv6 rules..."for ip in $ipv6_list; doif [[ ! -z "$ip" ]]; thenecho "Adding UFW rule for $ip..."sudo ufw allow from "$ip" to any app "Nginx HTTPS" comment "Cloudflare"fidone}# Apply rulesapply_ufw_rulesecho "All UFW rules have been added." -
Press Ctrl + O to save the file and Ctrl + X to exit.
-
Make the script executable and run it:
Terminal window chmod +x add_cloudflare_ufw_rules.sh && ./add_cloudflare_ufw_rules.sh -
Verify that the rules were added successfully:
Terminal window sudo ufw statusYou should see something similar to the following output:
Terminal window Status: activeTo Action From-- ------ ----OpenSSH LIMIT Anywhere # SSH (Rate Limited)Nginx HTTPS ALLOW 173.245.48.0/20 # CloudflareNginx HTTPS ALLOW 103.21.244.0/22 # CloudflareNginx HTTPS ALLOW 103.22.200.0/22 # CloudflareNginx HTTPS ALLOW 103.31.4.0/22 # CloudflareNginx HTTPS ALLOW 141.101.64.0/18 # CloudflareNginx HTTPS ALLOW 108.162.192.0/18 # CloudflareNginx HTTPS ALLOW 190.93.240.0/20 # CloudflareNginx HTTPS ALLOW 188.114.96.0/20 # CloudflareNginx HTTPS ALLOW 197.234.240.0/22 # CloudflareNginx HTTPS ALLOW 198.41.128.0/17 # CloudflareNginx HTTPS ALLOW 162.158.0.0/15 # CloudflareNginx HTTPS ALLOW 104.16.0.0/13 # CloudflareNginx HTTPS ALLOW 104.24.0.0/14 # CloudflareNginx HTTPS ALLOW 172.64.0.0/13 # CloudflareNginx HTTPS ALLOW 131.0.72.0/22 # CloudflareOpenSSH (v6) LIMIT Anywhere (v6) # SSH (Rate Limited)Nginx HTTPS (v6) ALLOW 2400:cb00::/32 # CloudflareNginx HTTPS (v6) ALLOW 2606:4700::/32 # CloudflareNginx HTTPS (v6) ALLOW 2803:f800::/32 # CloudflareNginx HTTPS (v6) ALLOW 2405:b500::/32 # CloudflareNginx HTTPS (v6) ALLOW 2405:8100::/32 # CloudflareNginx HTTPS (v6) ALLOW 2a06:98c0::/29 # CloudflareNginx HTTPS (v6) ALLOW 2c0f:f248::/32 # Cloudflare
Authenticated Origin Pulls
Authenticated Origin Pulls (AOP) helps ensure requests to your origin server come from the Cloudflare network, which provides an additional layer of security on top of Full or Full (strict) encryption modes.
This authentication becomes particularly important with the Cloudflare Web Application Firewall (WAF). Together with the WAF, you can make sure that all traffic is evaluated before receiving a response from your origin server.
-
Download the Cloudflare certificate and move it to the
/etc/nginx/certsdirectory:Terminal window wget https://developers.cloudflare.com/ssl/static/authenticated_origin_pull_ca.pemsudo mkdir -p /etc/nginx/certssudo mv authenticated_origin_pull_ca.pem /etc/nginx/certs/cloudflare.crt -
Open your configuration file for your site, replacing
example.comwith your domain name:Terminal window sudo nano /etc/nginx/sites-available/example.com -
Add the following lines to the server block, replacing
example.comwith your domain name:/etc/nginx/sites-available/example.com server {listen 443 ssl;listen [::]:443 ssl;http2 on;server_name example.com;root /var/www/example.com;index index.html;location / {try_files $uri $uri/ =404;}# SSL certificatesssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;# HSTS (ngx_http_headers_module is required) (63072000 seconds)add_header Strict-Transport-Security "max-age=63072000" always;# Modern configurationssl_protocols TLSv1.3;ssl_ecdh_curve X25519:prime256v1:secp384r1;ssl_prefer_server_ciphers off;# Authenticated Origin Pulls (mTLS)ssl_verify_client on;ssl_client_certificate /etc/nginx/certs/cloudflare.crt;} -
Press Ctrl + O to save the file and Ctrl + X to exit.
-
Check the configuration and reload
nginxto apply the changes:Terminal window sudo nginx -t && sudo systemctl reload nginx
Restoring original visitor IPs
When your website traffic is routed through the Cloudflare network, they act as a reverse proxy. This allows Cloudflare to speed up page load time by routing packets more efficiently and caching static resources (images, JavaScript, CSS, etc.). As a result, when responding to requests and logging them, your origin server returns a Cloudflare IP address.
To restore the original visitor IP addresses in your Nginx logs, you can use the ngx_http_realip_module to set the correct client IP address based on the CF-Connecting-IP header added by Cloudflare.
-
Open your Nginx configuration file:
Terminal window sudo nano /etc/nginx/nginx.conf -
Add the following lines within the
httpblock to set the real IP from Cloudflare:/etc/nginx/nginx.conf ### Logging Settings##set_real_ip_from 173.245.48.0/20;set_real_ip_from 103.21.244.0/22;set_real_ip_from 103.22.200.0/22;set_real_ip_from 103.31.4.0/22;set_real_ip_from 141.101.64.0/18;set_real_ip_from 108.162.192.0/18;set_real_ip_from 190.93.240.0/20;set_real_ip_from 188.114.96.0/20;set_real_ip_from 197.234.240.0/22;set_real_ip_from 198.41.128.0/17;set_real_ip_from 162.158.0.0/15;set_real_ip_from 104.16.0.0/13;set_real_ip_from 104.24.0.0/14;set_real_ip_from 172.64.0.0/13;set_real_ip_from 131.0.72.0/22;set_real_ip_from 2400:cb00::/32;set_real_ip_from 2606:4700::/32;set_real_ip_from 2803:f800::/32;set_real_ip_from 2405:b500::/32;set_real_ip_from 2405:8100::/32;set_real_ip_from 2a06:98c0::/29;set_real_ip_from 2c0f:f248::/32;real_ip_header CF-Connecting-IP;#real_ip_header X-Forwarded-For;log_format custom '$remote_addr - $remote_user [$time_local] ''"$request" $status $body_bytes_sent ''"$http_referer" "$http_user_agent" ''$http_cf_connecting_ip "$http_x_forwarded_for"';access_log /var/log/nginx/access.log;access_log /var/log/nginx/access.log custom;error_log /var/log/nginx/error.log warn; -
Press Ctrl + O to save the file and Ctrl + X to exit.
-
Check the configuration and reload
nginxto apply the changes:Terminal window sudo nginx -t && sudo systemctl reload nginx
Nginx, part 2
Prevent processing requests with undefined server names
When Nginx receives a request that doesn’t match any defined server names, it defaults to the first server block defined in the configuration. This can lead to unintended behavior, such as serving content from the wrong site or exposing sensitive information. To prevent this, you can create a catch-all server block that rejects requests with undefined server names.
If you’re using Cloudflare, you should block all traffic that doesn’t originate from Cloudflare IPs.
server { # Match all HTTP requests on port 80 and close the connection listen 80 default_server; listen [::]:80 default_server;
# Catch-all server name server_name _;
# Close the connection without sending a response return 444;}
server { # Match all HTTPS requests on port 443 and reject the SSL handshake listen 443 ssl default_server; listen [::]:443 ssl default_server;
http2 on;
# Catch-all server name server_name _;
# Provide a dummy SSL certificate and key for the handshake # These can be self-signed or a generic certificate. include snippets/snakeoil.conf;
# Reject the SSL handshake immediately ssl_reject_handshake on;}Disable server tokens
To enhance the security of your Nginx server, you can disable server tokens, which prevents Nginx from revealing its version number in error pages and HTTP headers.
-
Open your Nginx configuration file:
Terminal window sudo nano /etc/nginx/nginx.conf -
Find the
server_tokensdirective within thehttpblock and set it tooff:/etc/nginx/nginx.conf http {### Basic Settings##sendfile on;tcp_nopush on;types_hash_max_size 2048;# server_tokens off;server_tokens off;...} -
Press Ctrl + O to save the file and Ctrl + X to exit.
-
Check the configuration and reload
nginxto apply the changes:Terminal window sudo nginx -t && sudo systemctl reload nginx
Add common security headers
You can enhance the security of your web server by adding common security headers to your Nginx configuration.
Some of these may be added automatically if you use Cloudflare, but some of the ones added are depreciated and they’re still missing the more modern ones, so it’s still a good practice to include all of them in your server configuration.
-
Open your configuration file for your site, replacing
example.comwith your domain name:Terminal window sudo nano /etc/nginx/sites-available/example.com -
Add the following lines to the
serverblock, replacingexample.comwith your domain name:/etc/nginx/sites-available/example.com server {listen 443 ssl;listen [::]:443 ssl;http2 on;server_name example.com;root /var/www/example.com;index index.html;location / {try_files $uri $uri/ =404;}# Common security headersadd_header X-Content-Type-Options "nosniff" always;add_header Referrer-Policy "strict-origin-when-cross-origin" always;add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;add_header Cross-Origin-Opener-Policy "same-origin" always;add_header Cross-Origin-Embedder-Policy "require-corp" always;add_header Cross-Origin-Resource-Policy "same-origin" always;# SSL certificatesssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;# HSTS (ngx_http_headers_module is required) (63072000 seconds)add_header Strict-Transport-Security "max-age=63072000" always;# Modern configurationssl_protocols TLSv1.3;ssl_ecdh_curve X25519:prime256v1:secp384r1;ssl_prefer_server_ciphers off;# Authenticated Origin Pulls (mTLS)ssl_verify_client on;ssl_client_certificate /etc/nginx/certs/cloudflare.crt;}Content-Security-Policyis not included in this list because it requires careful configuration based on your website’s specific needs. An incorrect CSP can break functionality on your website. Consider implementing CSP separately after thorough testing. -
Press Ctrl + O to save the file and Ctrl + X to exit.
-
Check the configuration and reload
nginxto apply the changes:Terminal window sudo nginx -t && sudo systemctl reload nginx
For more information on these headers, please visit HTTP headers - HTTP MDN.
Use as a reverse proxy
You may use Nginx as a reverse proxy to terminate HTTPS requests and forward to an insecure application server (e.g., a web application running on a different port or in a Docker container).
-
Create a new configuration file for your site, replacing
example.comwith your domain name:Terminal window sudo nano /etc/nginx/sites-available/example.com -
Copy and paste the following configuration into the file, replacing
example.comwith your domain name andhttp://localhost:8080with the address of your application server:/etc/nginx/sites-available/example.com server {listen 443 ssl;listen [::]:443 ssl;http2 on;server_name example.com;location / {proxy_pass http://localhost:8080;# Preserve client informationproxy_set_header Host $host;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Host $host;proxy_set_header X-Forwarded-Proto $scheme;# RFC7239 Forwarded header (for both IPv4 and IPv6)proxy_set_header Forwarded "for=$remote_addr;host=$host;proto=$scheme";# Optional WebSocket supportproxy_http_version 1.1;proxy_set_header Upgrade $http_upgrade;proxy_set_header Connection "upgrade";}# SSL certificatesssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;# HSTS (ngx_http_headers_module is required) (63072000 seconds)add_header Strict-Transport-Security "max-age=63072000" always;# Modern configurationssl_protocols TLSv1.3;ssl_ecdh_curve X25519:prime256v1:secp384r1;ssl_prefer_server_ciphers off;# Authenticated Origin Pulls (mTLS)ssl_verify_client on;ssl_client_certificate /etc/nginx/certs/cloudflare.crt;}X-Forwarded-For,X-Forwarded-Host, andX-Forwarded-Protowill eventually be replaced by theForwardedheader as per RFC 7239, but many applications and frameworks still rely on the older headers for compatibility.X-Real-IPis non-standard, but widely used by many applications to obtain the client’s real IP address.Always consult your application server’s documentation for the recommended proxy settings.
-
Press Ctrl + O to save the file and Ctrl + X to exit.
-
Check the configuration and reload
nginxto apply the changes:Terminal window sudo nginx -t && sudo systemctl reload nginx
For more information on these headers, please visit HTTP headers - HTTP MDN.
Have files downloaded as attachments
By default, most web browsers will attempt to open any plain text files as web pages. To have them download everything as an attachment instead:
-
Open your configuration file for your site, replacing
example.comwith your domain name:Terminal window sudo nano /etc/nginx/sites-available/example.com -
Add the following lines to the
serverblock, replacingexample.comwith your domain name:/etc/nginx/sites-available/example.com server {listen 443 ssl;listen [::]:443 ssl;http2 on;server_name example.com;root /var/www/example.com;index index.html;location / {try_files $uri $uri/ =404;}location ^~ /downloads/ {# Your existing root / alias / try_files etc. stay the sameadd_header Content-Disposition 'attachment';# Optional but recommended – prevents some browsers from still trying to render itdefault_type application/octet-stream;}# SSL certificatesssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;# HSTS (ngx_http_headers_module is required) (63072000 seconds)add_header Strict-Transport-Security "max-age=63072000" always;# Modern configurationssl_protocols TLSv1.3;ssl_ecdh_curve X25519:prime256v1:secp384r1;ssl_prefer_server_ciphers off;# Authenticated Origin Pulls (mTLS)ssl_verify_client on;ssl_client_certificate /etc/nginx/certs/cloudflare.crt;} -
Press Ctrl + O to save the file and Ctrl + X to exit.
-
Check the configuration and reload
nginxto apply the changes:Terminal window sudo nginx -t && sudo systemctl reload nginx
Use snippets
Nginx snippets are reusable configuration files that can be included in multiple server blocks or locations. They help to keep your configuration DRY (Don’t Repeat Yourself) and make it easier to manage.
Proxy headers snippet
-
Create a new snippet file for proxy headers:
Terminal window sudo nano /etc/nginx/snippets/proxy-headers.conf -
Copy and paste the following lines into the file:
/etc/nginx/snippets/proxy-headers.conf # Preserve client informationproxy_set_header Host $host;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Host $host;proxy_set_header X-Forwarded-Proto $scheme;# RFC7239 Forwarded header (for both IPv4 and IPv6)proxy_set_header Forwarded "for=$remote_addr;host=$host;proto=$scheme";# Optional WebSocket supportproxy_http_version 1.1;proxy_set_header Upgrade $http_upgrade;proxy_set_header Connection "upgrade"; -
Press Ctrl + O to save the file and Ctrl + X to exit.
Security headers snippet
-
Create a new snippet file for security headers:
Terminal window sudo nano /etc/nginx/snippets/security-headers.conf -
Copy and paste the following lines into the file:
/etc/nginx/snippets/security-headers.conf # Common security headersadd_header X-Content-Type-Options "nosniff" always;add_header Referrer-Policy "strict-origin-when-cross-origin" always;add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;add_header Cross-Origin-Opener-Policy "same-origin" always;add_header Cross-Origin-Embedder-Policy "require-corp" always;add_header Cross-Origin-Resource-Policy "same-origin" always; -
Press Ctrl + O to save the file and Ctrl + X to exit.
SSL snippet
-
Create a new snippet file for SSL configuration:
Terminal window sudo nano /etc/nginx/snippets/ssl.conf -
Copy and paste the following lines into the file, replacing
example.comwith your domain name:/etc/nginx/snippets/ssl.conf # SSL certificatesssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;# HSTS (ngx_http_headers_module is required) (63072000 seconds)add_header Strict-Transport-Security "max-age=63072000" always;# Modern configurationssl_protocols TLSv1.3;ssl_ecdh_curve X25519:prime256v1:secp384r1;ssl_prefer_server_ciphers off;# Authenticated Origin Pulls (mTLS)ssl_verify_client on;ssl_client_certificate /etc/nginx/certs/cloudflare.crt; -
Press Ctrl + O to save the file and Ctrl + X to exit.
Update site configuration
-
Open your configuration file for your site, replacing
example.comwith your domain name:Terminal window sudo nano /etc/nginx/sites-available/example.com -
Make the following changes in the file, replacing
example.comwith your domain name:/etc/nginx/sites-available/example.com server {listen 443 ssl;listen [::]:443 ssl;http2 on;server_name example.com;root /var/www/example.com;index index.html;location / {try_files $uri $uri/ =404;}# Common security headersadd_header X-Content-Type-Options "nosniff" always;add_header Referrer-Policy "strict-origin-when-cross-origin" always;add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;add_header Cross-Origin-Opener-Policy "same-origin" always;add_header Cross-Origin-Embedder-Policy "require-corp" always;add_header Cross-Origin-Resource-Policy "same-origin" always;# Security headers snippetinclude snippets/security-headers.conf;# SSL certificatesssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;# HSTS (ngx_http_headers_module is required) (63072000 seconds)add_header Strict-Transport-Security "max-age=63072000" always;# Modern configurationssl_protocols TLSv1.3;ssl_ecdh_curve X25519:prime256v1:secp384r1;ssl_prefer_server_ciphers off;# Authenticated Origin Pulls (mTLS)ssl_verify_client on;ssl_client_certificate /etc/nginx/certs/cloudflare.crt;# SSL snippetinclude snippets/ssl.conf;} -
Press Ctrl + O to save the file and Ctrl + X to exit.
-
Check the configuration and reload
nginxto apply the changes:Terminal window sudo nginx -t && sudo systemctl reload nginx -
Repeat these steps for any other sites using the same configuration.
Enable compression
Gzip
-
Open your Nginx configuration file:
Terminal window sudo nano /etc/nginx/nginx.conf -
Add the following line within the
httpblock to serve pre-compressed gzip files:/etc/nginx/nginx.conf ### Gzip Settings##gzip on;gzip_static on; -
Uncomment and adjust the
gzip_comp_levelandgzip_typesdirectives to optimize compression for your specific use case:/etc/nginx/nginx.conf ### Gzip Settings##gzip on;gzip_static on;# gzip_vary on;# gzip_proxied any;gzip_comp_level 6;# gzip_buffers 16 8k;# gzip_http_version 1.1;gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; -
Press Ctrl + O to save the file and Ctrl + X to exit.
-
Check the configuration and reload
nginxto apply the changes:Terminal window sudo nginx -t && sudo systemctl reload nginx
Brotli
-
Install the Brotli modules for Nginx and verify that they’re installed correctly:
Terminal window sudo apt install libnginx-mod-http-brotli-filter libnginx-mod-http-brotli-staticsudo ls /usr/lib/nginx/modules/ | grep brotlisudo nginx -t && sudo service nginx restart -
Open your Nginx configuration file:
Terminal window sudo nano /etc/nginx/nginx.conf -
Add the following line within the
httpblock to enable Brotli compression and serve pre-compressed Brotli files:/etc/nginx/nginx.conf ### Gzip Settings##gzip on;gzip_static on;# gzip_vary on;# gzip_proxied any;gzip_comp_level 6;# gzip_buffers 16 8k;# gzip_http_version 1.1;gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss>### Brotli Settings##brotli on;brotli_static on; -
Press Ctrl + O to save the file and Ctrl + X to exit.
-
Check the configuration and reload
nginxto apply the changes:Terminal window sudo nginx -t && sudo systemctl reload nginx
References
The following sources were used while writing this guide: