When setting up nginx as a reverse proxy with HTTPS termination, you're encountering mixed content warnings because your backend services are serving resources with hardcoded HTTP URLs. This common architecture pattern - where nginx handles SSL termination while communicating with backend servers over HTTP - requires special configuration to avoid these warnings.
The mixed content warnings occur because:
- Your backend services generate HTML with absolute HTTP URLs (e.g., http://domain.com/style.css)
- Browsers receive these requests through HTTPS but see insecure HTTP resources
- Modern browsers block these mixed content requests for security reasons
Here's an enhanced version of your configuration that solves multiple aspects of the problem:
server {
listen 443 ssl;
server_name domain.com;
# SSL configuration
ssl_certificate /path/to/your/certificate.crt;
ssl_certificate_key /path/to/your/private.key;
# 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;
# Service1 configuration with content rewriting
location /service1/ {
proxy_pass http://192.168.1.101/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
# Standard proxy headers
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;
# Content rewriting to fix URLs
proxy_set_header Accept-Encoding "";
sub_filter_once off;
sub_filter_types *;
sub_filter 'http://domain.com' 'https://domain.com';
sub_filter 'http://$host' 'https://$host';
}
# Similar configuration for service2
location /service2/ {
proxy_pass http://192.168.1.102/;
# ... same configuration as above ...
}
}
server {
listen 80;
server_name domain.com;
return 301 https://$host$request_uri;
}
1. HTTP to HTTPS Redirect
The separate server block ensures all HTTP traffic gets redirected to HTTPS, preventing any accidental HTTP access.
2. Content Rewriting with sub_filter
The sub_filter directives actively rewrite HTTP URLs to HTTPS in the response bodies. This handles cases where backends hardcode URLs.
3. Proper Header Forwarding
X-Forwarded-Proto tells backends the original request was HTTPS, allowing them to generate correct URLs if they're protocol-aware.
4. Security Headers
Additional headers like HSTS enforce HTTPS usage and improve overall security.
For more complex scenarios, consider these additions:
# For WebSocket support
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
# Custom error pages
proxy_intercept_errors on;
error_page 502 /custom_502.html;
location = /custom_502.html {
root /usr/share/nginx/html;
internal;
}
# Buffering and timeouts
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 16k;
proxy_busy_buffers_size 24k;
After implementing these changes:
- Test with browser developer tools (Network tab) to verify all resources load via HTTPS
- Check for any remaining mixed content warnings in console
- Verify redirects work properly using curl:
curl -I http://domain.com
- Test backend connectivity:
curl -H "Host: domain.com" http://localhost/service1/
If content rewriting isn't sufficient:
1. Header Injection
Some frameworks respect the X-Forwarded-Proto header if properly configured:
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Port $server_port;
2. Application-Level Fixes
For maximum compatibility, consider updating applications to:
- Use protocol-relative URLs (//domain.com/resource)
- Read the X-Forwarded-Proto header
- Support configuration of base URLs
When implementing Nginx as a reverse proxy with HTTPS termination, many developers encounter mixed content warnings when the backend servers return HTTP-absolute URLs. The browser detects this discrepancy between the secure HTTPS page and insecure HTTP resources, blocking them for security reasons.
# Typical error message in browser console:
Mixed Content: The page at 'https://domain.com/service1' was loaded over HTTPS,
but requested an insecure resource 'http://domain.com/service1/style.css'
Here's a comprehensive configuration that solves multiple aspects of the mixed content problem:
server {
listen 443 ssl;
server_name domain.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
location /service1/ {
proxy_pass http://192.168.1.101/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
# Critical headers for mixed content resolution
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_set_header X-Forwarded-Port $server_port;
# Content rewriting
proxy_set_header Accept-Encoding "";
sub_filter_types *;
sub_filter 'http://domain.com' 'https://domain.com';
sub_filter_once off;
}
location /service2/ {
proxy_pass http://192.168.1.102/;
# Similar configuration as above
}
}
1. Protocol Headers: The X-Forwarded-Proto
header informs backend servers about the original request scheme.
2. Content Rewriting: The sub_filter
directive dynamically modifies response content, converting HTTP URLs to HTTPS.
3. WebSocket Support: The Upgrade
headers ensure WebSocket connections work through the proxy.
For applications that use JavaScript to construct URLs:
# In your Nginx config
location / {
# ... other proxy settings ...
# Set a cookie that JavaScript can read
add_header Set-Cookie "frontend_https=1; Path=/";
# Or inject a meta tag
sub_filter '</head>' '<meta name="frontend_https" content="true"></head>';
}
Then in your JavaScript:
const isHTTPS = document.cookie.includes('frontend_https=1') ||
document.querySelector('meta[name="frontend_https"]') !== null;
const API_BASE = isHTTPS ? 'https://domain.com' : 'http://domain.com';
Verify your setup with these commands:
# Check Nginx configuration syntax
sudo nginx -t
# Check headers
curl -I https://domain.com/service1/
# Test content rewriting
curl https://domain.com/service1/ | grep 'https://domain.com'