When setting up NGINX as a reverse proxy, a common requirement is to expose HTTPS to clients while communicating with backend servers over plain HTTP. This SSL termination scenario improves performance by offloading encryption/decryption from application servers.
Here's a working solution that handles both HTTP-to-HTTPS redirection and proper HTTPS communication with clients:
server { listen 80; server_name example.com; return 301 https://$host$request_uri; } server { listen 443 ssl; server_name example.com; ssl_certificate /path/to/cert.pem; ssl_certificate_key /path/to/key.pem; location / { proxy_pass http://backend_server; 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; # Recommended timeouts proxy_connect_timeout 60s; proxy_send_timeout 60s; proxy_read_timeout 60s; send_timeout 60s; } }
The critical components that make this work:
# Force HTTPS return 301 https://$host$request_uri; # Proper protocol headers proxy_set_header X-Forwarded-Proto $scheme; # Backend communication proxy_pass http://backend_server;
Mixed Content Issues: Ensure your backend generates URLs with protocol-relative paths (//example.com/resource) or uses HTTPS in absolute URLs.
WebSocket Support: For WebSocket connections, add these directives:
proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade";
For better performance and security:
# SSL optimization ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; # Security headers add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; add_header X-Content-Type-Options nosniff; add_header X-Frame-Options SAMEORIGIN;
Verify with these commands:
sudo nginx -t sudo systemctl reload nginx curl -I http://example.com # Should return 301 redirect curl -Ik https://example.com # Should return 200 OK
When setting up NGINX as a reverse proxy, one common scenario involves accepting HTTPS connections from clients while communicating with backend servers over HTTP. The configuration seems straightforward, but there are important nuances in how NGINX handles protocol transitions.
The original configuration attempts to:
- Accept HTTP requests on port 80
- Redirect to HTTPS (though this part is missing from the shown config)
- Proxy to backend over HTTP
- Return the response over HTTPS
server { listen 80; server_name example.com; return 301 https://$host$request_uri; } server { listen 443 ssl; server_name example.com; ssl_certificate /path/to/cert.pem; ssl_certificate_key /path/to/key.pem; location / { proxy_pass http://backend; 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; } }
The key issue in the original configuration is missing the HTTPS server block entirely. Here's what needs to be addressed:
1. Missing SSL Termination
The configuration shows only port 80 handling. For HTTPS proxying, you need:
listen 443 ssl; ssl_certificate /path/to/cert.pem; ssl_certificate_key /path/to/key.pem;
2. Protocol Headers
Ensure proper headers are passed to the backend:
proxy_set_header X-Forwarded-Proto https; proxy_set_header X-Forwarded-Port 443;
3. HTTP to HTTPS Redirect
The redirect should happen before any proxying:
server { listen 80; server_name example.com; return 301 https://$host$request_uri; }
Here's a full example that implements all required functionality:
# HTTP to HTTPS redirect server { listen 80; server_name example.com; return 301 https://$host$request_uri; } # HTTPS server block server { listen 443 ssl http2; server_name example.com; # SSL configuration ssl_certificate /etc/nginx/ssl/example.com.crt; ssl_certificate_key /etc/nginx/ssl/example.com.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; # Security headers add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; add_header X-Frame-Options SAMEORIGIN; add_header X-Content-Type-Options nosniff; # Proxy configuration location / { proxy_pass http://backend-server; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; 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 https; proxy_cache_bypass $http_upgrade; } }
After implementing these changes:
- Restart NGINX:
sudo systemctl restart nginx
- Test HTTP to HTTPS redirect:
curl -I http://example.com
- Verify HTTPS proxying:
curl -k https://example.com
- Check backend logs to confirm protocol headers
For production environments, consider adding:
- OCSP stapling for better SSL performance
- HTTP/2 support for modern browsers
- Proper caching headers
- Rate limiting
- Load balancing for multiple backends
Remember that while this configuration handles the protocol transition, you should also ensure your backend application is aware it's being accessed through HTTPS, as some frameworks need explicit configuration to handle forwarded protocol headers properly.