Overview
Running Nginx as a front‑end web server on Ubuntu is a common choice for high‑traffic applications. However, the default installation leaves several attack surfaces wide open: weak TLS ciphers, missing security headers, and no rate‑limiting, to name a few. This tutorial walks an SRE through seven practical steps to harden Nginx without sacrificing performance.
1. Enforce Strong TLS
TLS is the first line of defense. Use TLS 1.3 where possible and drop legacy protocols. A minimal nginx.conf
snippet for modern TLS looks like this:
server { listen 443 ssl http2; server_name example.com; ssl_certificate /etc/ssl/certs/example.com.crt; ssl_certificate_key /etc/ssl/private/example.com.key; # TLS 1.3 only, fallback to TLS 1.2 with strong ciphers ssl_protocols TLSv1.3 TLSv1.2; ssl_prefer_server_ciphers on; ssl_ciphers "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256"; # Enable OCSP stapling for faster revocation checks ssl_stapling on; ssl_stapling_verify on; resolver 8.8.8.8 8.8.4.4 valid=300s; resolver_timeout 5s; }
- Why it matters – Strong ciphers prevent downgrade attacks and improve forward secrecy.
- Tip – Test your configuration with
sslscan
or Qualys SSL Labs.
2. Harden the Firewall
A properly scoped firewall reduces the attack surface dramatically. On Ubuntu, ufw
(Uncomplicated Firewall) is a friendly wrapper around iptables
.
# Allow HTTP/HTTPS traffic only sudo ufw allow 80/tcp sudo ufw allow 443/tcp # Deny everything else sudo ufw default deny incoming sudo ufw default allow outgoing # Enable the firewall sudo ufw enable
If you run SSH on a non‑standard port, add a rule for it as well. Keep the rule set minimal; each open port is a potential entry point.
3. Deploy Fail2Ban for Brute‑Force Protection
Fail2Ban watches log files and bans IPs that exhibit suspicious behavior. A simple jail for Nginx looks like this (/etc/fail2ban/jail.d/nginx.conf
):
[nginx-http-auth] enabled = true filter = nginx-http-auth logpath = /var/log/nginx/error.log maxretry = 5 bantime = 3600
The accompanying filter (/etc/fail2ban/filter.d/nginx-http-auth.conf
) matches typical 401/403 responses. Restart the service:
sudo systemctl restart fail2ban
Now any IP that fails to authenticate more than five times within an hour gets blocked for an hour.
4. Add Security Headers
HTTP security headers tell browsers how to treat your content. Insert the following block into the server
context:
add_header X-Content-Type-Options "nosniff" always; add_header X-Frame-Options "DENY" always; add_header X-XSS-Protection "1; mode=block" always; add_header Referrer-Policy "no-referrer-when-downgrade" always; add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline';" always;
-
X‑Content‑Type‑Options
stops MIME sniffing. -
X‑Frame‑Options
prevents click‑jacking. -
Content‑Security‑Policy
mitigates XSS.
These headers are lightweight and add virtually no latency.
5. Limit Request Size and Rate
Large payloads and burst traffic can exhaust resources. Use client_max_body_size
to cap uploads and limit_req_zone
/limit_req
for rate limiting.
# Global limits (in http context) limit_req_zone $binary_remote_addr zone=one:10m rate=30r/s; server { ... client_max_body_size 5M; location /login { limit_req zone=one burst=5 nodelay; proxy_pass http://backend; } }
This configuration allows a maximum of 30 requests per second per IP, with a burst of five extra requests for legitimate spikes.
6. Run Nginx with Systemd Hardening
Systemd offers several hardening options that sandbox the service. Edit the override file (/etc/systemd/system/nginx.service.d/hardening.conf
):
[Service] # Drop privileges User=www-data Group=www-data # Restrict filesystem access ProtectSystem=full ProtectHome=true ReadWriteDirectories=-/var/log/nginx # Limit capabilities CapabilityBoundingSet=CAP_NET_BIND_SERVICE AmbientCapabilities=CAP_NET_BIND_SERVICE # Prevent core dumps LimitCORE=0
Reload systemd and restart Nginx:
sudo systemctl daemon-reload sudo systemctl restart nginx
These settings ensure Nginx cannot write to arbitrary paths, cannot gain extra capabilities, and cannot produce core dumps that might leak memory.
7. Automate Patching and Backups
Security is a moving target. Use unattended-upgrades
for automatic security updates and a simple rsnapshot
configuration for daily Nginx config backups.
# Install unattended upgrades sudo apt-get install unattended-upgrades sudo dpkg-reconfigure --priority=low unattended-upgrades
Create a snapshot script (/etc/cron.daily/nginx-backup
):
#!/bin/sh rsnapshot daily /etc/nginx /var/log/nginx
Make it executable and ensure rsnapshot
is configured with a retention policy that matches your RPO.
Quick Checklist
- [ ] TLS 1.3 enabled, weak ciphers disabled
- [ ] Firewall only allows 80/443 (and custom SSH)
- [ ] Fail2Ban active for Nginx auth failures
- [ ] Security headers added to every response
- [ ] Request size capped, rate limiting in place
- [ ] Systemd hardening flags applied
- [ ] Automatic security patches and daily config backups
Conclusion
Hardening Nginx on Ubuntu is a series of low‑effort, high‑impact steps. By tightening TLS, locking down the host firewall, adding Fail2Ban, sprinkling security headers, and sandboxing the service with systemd, you dramatically reduce the attack surface while keeping latency low. For deeper dives into Ubuntu server hardening, consider checking out resources like the official Ubuntu Security Guide, and if you need a managed partner to audit or host your hardened stack, https://lacidaweb.com offers practical assistance without the sales push.
Top comments (0)