How to Configure NGINX to Proxy HTTPS Requests to HTTP Backend with SSL Termination


2 views

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:

  1. Accept HTTP requests on port 80
  2. Redirect to HTTPS (though this part is missing from the shown config)
  3. Proxy to backend over HTTP
  4. 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:

  1. Restart NGINX: sudo systemctl restart nginx
  2. Test HTTP to HTTPS redirect: curl -I http://example.com
  3. Verify HTTPS proxying: curl -k https://example.com
  4. 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.