Nginx Production Best Practices: Security, Performance, High Availability and Operations
A production-ready Nginx deployment requires more than a working configuration file. It requires secure TLS settings, controlled upstream access, reliable application integration, sensible resource limits, observability, configuration validation and a tested recovery strategy.
This independent article covers practical Nginx hardening and operational practices for Linux servers, reverse proxies, APIs, containerised applications and cloud environments.
1. Keep Nginx Updated and Minimise the Attack Surface
Install Nginx from a trusted, maintained package repository. Apply security updates regularly and verify the version, build options and enabled modules.
On Ubuntu or Debian:
sudo apt update
sudo apt upgrade
nginx -v
nginx -V
sudo nginx -t
Best practices:
- Use supported Nginx releases and operating system packages.
- Remove unnecessary third-party modules and avoid untrusted binaries.
- Track security advisories and package updates.
- Restrict administrative access through SSH, VPN or private networks.
- Run application processes with dedicated, unprivileged service accounts.
- Avoid exposing application ports directly to the public internet.
Nginx commonly uses a privileged master process and unprivileged worker processes. Avoid changing this process model without understanding the implications.
Official reference: https://nginx.org/en/docs/beginners_guide.html
2. Harden TLS and HTTPS
HTTPS should be mandatory for public production websites, APIs and administrative interfaces.
A typical configuration is:
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
location / {
proxy_pass http://127.0.0.1:8000;
}
}
Production recommendations:
- Use valid certificates from a trusted certificate authority.
- Automate certificate renewal and monitor expiry.
- Permit TLS 1.2 and TLS 1.3 unless a documented compatibility requirement dictates otherwise.
- Protect private keys with appropriate ownership and file permissions.
- Test certificate chains, supported protocols and cipher negotiation.
- Configure HTTP-to-HTTPS redirection.
- Enable HTTP Strict Transport Security (HSTS) only after confirming that HTTPS is reliable across the intended domain scope.
Do not copy arbitrary cipher-suite lists from old tutorials. Use maintained security guidance appropriate to your Nginx and OpenSSL versions.
Official reference: https://nginx.org/en/docs/http/ngx_http_ssl_module.html
3. Configure a Secure Reverse Proxy
Nginx should normally be the public entry point, while application runtimes listen on loopback addresses or private networks.
Example:
location /api/ {
proxy_pass http://127.0.0.1:8000;
proxy_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-Proto $scheme;
proxy_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
Important considerations:
- Bind applications to loopback or private interfaces where possible.
- Set explicit connection and response timeouts.
- Forward only the headers required by the application.
- Configure trusted proxy addresses correctly when another load balancer or CDN precedes Nginx.
- Prevent clients from spoofing forwarding headers.
- Use separate upstream definitions for applications with different operational requirements.
- Verify URI handling carefully because the trailing slash in
proxy_passcan affect how request paths are forwarded.
Timeouts should reflect actual workload behaviour. A short timeout may interrupt legitimate long-running requests, while an excessively long timeout may consume resources unnecessarily.
Official reference: https://nginx.org/en/docs/http/ngx_http_proxy_module.html
4. Prevent Abuse with Rate and Connection Limits
Rate limiting helps protect login endpoints, expensive API operations and other sensitive routes from excessive requests.
Define a rate-limit zone in the http context:
http {
limit_req_zone $binary_remote_addr
zone=api_limit:10m rate=10r/s;
server {
listen 443 ssl;
server_name api.example.com;
location /api/ {
limit_req zone=api_limit burst=20 nodelay;
proxy_pass http://127.0.0.1:8000;
}
}
}
This illustrative configuration permits an average rate of 10 requests per second per client key, with a burst allowance of 20 requests. Excess requests may be rejected.
Production recommendations:
- Apply different limits to login, search, upload and general API endpoints.
- Test limits in a non-enforcing or dry-run mode where supported.
- Monitor rejected requests and adjust thresholds using real traffic data.
- Account for shared public IP addresses, such as corporate NAT gateways.
- Configure connection limits for resource-intensive routes.
- Use an upstream WAF or dedicated DDoS protection where necessary.
Nginx rate limiting is not a substitute for application-level abuse prevention or authentication.
Official references:
5. Tune Worker Processes and Connections
Nginx uses an event-driven architecture. Its worker processes handle client connections, and the appropriate configuration depends on CPU resources, file descriptor limits, network traffic and workload characteristics.
A reasonable starting point is:
worker_processes auto;
events {
worker_connections 2048;
}
These values are illustrative, not universal performance recommendations.
Consider the following:
- Start with
worker_processes autoand benchmark before changing it. - Calculate connection capacity while accounting for file descriptors and upstream connections.
- Review operating system limits, including open files and process limits.
- Monitor CPU saturation, memory use, dropped connections and latency.
- Avoid increasing connection limits without checking operating system and infrastructure capacity.
- Test under realistic concurrent traffic before tuning production settings.
The maximum theoretical connection count is not the same as the number of supported application requests. Reverse-proxy traffic may consume connections on both the client and upstream sides.
Official reference: https://nginx.org/en/docs/ngx_core_module.html
6. Configure Static Content, Compression and Caching
Static assets such as JavaScript, CSS, images and fonts should generally be served directly by Nginx or a CDN instead of passing through the application runtime.
Example:
location /assets/ {
root /var/www/example;
expires 30d;
add_header Cache-Control "public";
}
This example assumes that the assets are versioned or otherwise safe to cache for the configured duration. Verify the resulting filesystem path because root and alias behave differently.
Best practices:
- Use content-hashed filenames for frontend assets.
- Apply long cache lifetimes to immutable assets.
- Use shorter or no caching for personalised responses and sensitive content.
- Enable compression where it improves performance without excessive CPU overhead.
- Avoid compressing already compressed formats unnecessarily.
- Consider a CDN for globally distributed static content.
- Monitor cache hit rates when proxy caching is enabled.
Do not apply a long public cache lifetime indiscriminately to HTML documents, authentication responses or user-specific API data.
7. Handle WebSockets and Long-Lived Connections Correctly
Applications using WebSockets, event streaming, chat or real-time dashboards require additional proxy configuration.
Example:
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 443 ssl;
server_name app.example.com;
location /ws/ {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
proxy_read_timeout 3600s;
}
}
A long read timeout is appropriate only when the application needs long-lived connections. Application-level ping/pong messages and idle connection handling should also be designed properly.
For production:
- Validate WebSocket upgrade behaviour end to end.
- Configure idle timeouts across the load balancer, proxy and application.
- Set realistic connection and memory limits.
- Ensure graceful deployment behaviour for connected clients.
- Monitor active connections and abnormal disconnects.
Official reference: https://nginx.org/en/docs/http/websocket.html
8. Implement Reliable Logging and Monitoring
Nginx access and error logs are essential for diagnosing HTTP errors, upstream failures, latency and suspicious traffic.
Example access-log format:
log_format production
'$remote_addr [$time_iso8601] '
'"$request_method $uri $server_protocol" '
'$status $body_bytes_sent '
'request_time=$request_time '
'upstream_time=$upstream_response_time';
access_log /var/log/nginx/access.log production;
error_log /var/log/nginx/error.log warn;
Avoid logging query strings, authentication tokens, session identifiers or sensitive personal information unless there is a documented and protected requirement.
Monitor:
- HTTP 4xx and 5xx response rates.
- Upstream connection failures and response times.
- Request volume, bandwidth and active connections.
- CPU, memory, disk utilisation and file descriptors.
- Certificate expiry and renewal failures.
- Rate-limit rejections and suspicious traffic patterns.
Forward logs to a centralised platform where appropriate, and establish alerts for actionable failures rather than every isolated warning.
Official reference: https://nginx.org/en/docs/http/ngx_http_log_module.html
9. Validate Configuration and Deploy Safely
Never reload an untested configuration in production.
Recommended workflow:
sudo nginx -t
sudo nginx -T
sudo systemctl reload nginx
The first command checks configuration syntax and referenced files. The second displays the effective configuration, which may contain sensitive operational details. Restrict access to its output. The third requests a graceful reload.
Best practices:
- Store configuration in version control.
- Review changes through pull requests or an equivalent approval process.
- Validate configuration automatically in CI/CD.
- Back up the previous working configuration.
- Apply changes through controlled deployment automation.
- Verify application health, logs and error rates after deployment.
- Maintain a documented rollback procedure.
Nginx normally starts new workers with the new configuration and allows old workers to finish existing connections. If the new configuration cannot be applied, Nginx can retain its previous working configuration. Therefore, a successful command invocation should not replace post-deployment verification.
Official reference: https://nginx.org/en/docs/control.html
10. Design for High Availability
A single Nginx server is a single point of failure unless the surrounding architecture provides redundancy.
For important production services, consider:
- Multiple proxy instances across separate failure domains.
- A managed load balancer or another suitable traffic-distribution layer.
- Health checks and removal of unhealthy backends.
- Redundant application instances.
- Automated certificate management across proxy instances.
- Configuration consistency and controlled rollout procedures.
- Capacity planning for peak traffic and failover conditions.
- Tested disaster recovery and restoration procedures.
For a small application, a single Nginx instance with reliable backups may be sufficient. For critical services, redundancy should cover the complete request path, not just the web server.
11. Production Readiness Checklist
Security
- Supported software and security updates.
- HTTPS and certificate renewal.
- Private backend ports.
- Trusted proxy-header configuration.
- Appropriate rate and connection limits.
- Access controls and least-privilege permissions.
Performance
- Worker and file descriptor capacity.
- Appropriate timeouts.
- Static asset caching and compression.
- WebSocket and streaming tests.
- Load tests using realistic traffic.
Operations
- Centralised logging and monitoring.
- Configuration validation and version control.
- Graceful reload and rollback tests.
- Health checks and alerting.
- Backup and disaster recovery procedures.
12. Example Production Configuration
The configuration below assumes Ubuntu/Debian, Nginx, a domain with a valid TLS certificate, and a FastAPI application listening on 127.0.0.1:8000. It uses a single Nginx instance and can be adapted for Django, Node.js or other backends.
1. Install Nginx and obtain a TLS certificate
Replace example.com with your actual domain and configure its DNS A/AAAA records correctly.
sudo apt update
sudo apt install -y nginx certbot python3-certbot-nginx
sudo certbot --nginx -d example.com
2. Configure Nginx
Create the HTTP-level settings (rate-limit zone and a production log format) inside the http block, for example in /etc/nginx/conf.d/00-http.conf:
log_format production
'$remote_addr [$time_iso8601] '
'"$request_method $uri $server_protocol" '
'$status $body_bytes_sent '
'request_time=$request_time '
'upstream_time=$upstream_response_time';
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
Then define the server blocks in a site file such as /etc/nginx/sites-available/example.com and enable it:
server {
listen 80;
server_name example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
client_max_body_size 10m;
client_body_timeout 12s;
client_header_timeout 12s;
send_timeout 10s;
keepalive_timeout 30s;
access_log /var/log/nginx/example.access.log production;
error_log /var/log/nginx/example.error.log warn;
location /static/ {
alias /var/www/example/static/;
expires 30d;
add_header Cache-Control "public";
access_log off;
}
location /ws/ {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
proxy_read_timeout 3600s;
}
location / {
limit_req zone=api_limit burst=20 nodelay;
proxy_pass http://127.0.0.1:8000;
proxy_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-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}
3. Start the application and verify
sudo nginx -t
sudo systemctl enable --now nginx
# Run the application runtime on loopback only, for example:
# uvicorn main:app --host 127.0.0.1 --port 8000
sudo systemctl reload nginx
curl -i https://example.com/health
Conclusion
Production Nginx should be treated as a critical infrastructure component, not merely a configuration file. Secure the public entry point, isolate application runtimes, configure TLS and proxy headers correctly, tune resources using measurements, monitor operational behaviour and deploy changes through a controlled process.
Start with a minimal secure configuration, establish measurable performance and reliability targets, and introduce additional complexity only when the workload justifies it.