When running multiple websites on a single server with Nginx, a common pitfall occurs with HTTPS virtual hosts. The issue manifests when accessing a non-SSL configured domain (staging.com) via HTTPS, but receiving content from your SSL-enabled domain (production.com). This happens because Nginx defaults to the first SSL server block when no matching server_name is found for the requested domain.
Your current setup contains these key components:
# Default catch-all for HTTP
server {
listen 80;
server_name "";
return 444;
}
# Default catch-all for HTTPS (problematic version)
server {
listen 443 default_server ssl;
server_name "";
return 444;
}
The issue arises from the interaction between these default handlers and your specific site configurations.
Here's the corrected approach for your virtual host configuration:
# production.com - with SSL
server {
listen 443 ssl;
server_name production.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
# ... rest of your production config
}
# staging.com - HTTPS should terminate
server {
listen 443 ssl;
server_name staging.com;
ssl_certificate /dummy/path;
ssl_certificate_key /dummy/path;
return 444; # Connection closed without response
}
# HTTP to HTTPS redirect for production
server {
listen 80;
server_name production.com;
return 301 https://production.com$request_uri;
}
# Regular HTTP for staging
server {
listen 80;
server_name staging.com;
# ... existing staging config
}
The key improvements in this configuration:
- Explicit SSL server block for staging.com that immediately terminates the connection
- Proper separation of HTTP and HTTPS handling for each domain
- Removal of the problematic default_server directive for port 443
For better security, consider implementing a self-signed certificate for the staging HTTPS termination:
# staging.com - with self-signed cert
server {
listen 443 ssl;
server_name staging.com;
ssl_certificate /etc/nginx/ssl/staging-selfsigned.crt;
ssl_certificate_key /etc/nginx/ssl/staging-selfsigned.key;
# Optionally add SSL protocols for better security
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
return 444;
}
This approach provides better security than using dummy file paths while still achieving the desired behavior of terminating HTTPS connections for staging.
After implementing these changes:
- Always test with
nginx -t
before reloading - Clear your browser cache or use incognito mode when testing
- Check Nginx error logs if issues persist:
tail -f /var/log/nginx/error.log
When running multiple websites on a single Nginx instance with SSL, you might encounter situations where HTTPS requests to one domain (staging.com) incorrectly serve content from another domain's (production.com) web root. This occurs because Nginx selects the first SSL-enabled server block when no matching server_name is found for HTTPS requests.
Looking at your setup, I see you have:
# production.com.conf
server {
listen 443 ssl;
server_name production.com;
# ... SSL and other configs ...
}
# default_443.conf
server {
listen 443 default_server ssl;
server_name "";
return 444;
}
The core issue here is that your staging.com configuration lacks an SSL server block, while production.com has one. When users access https://staging.com, Nginx falls back to the production.com configuration because it's the only one that matches the SSL port.
Here's how to properly configure SSL virtual hosts in Nginx:
# staging.com.conf - Add this SSL server block
server {
listen 443 ssl;
server_name staging.com;
ssl_certificate /path/to/staging.crt;
ssl_certificate_key /path/to/staging.key;
return 444; # This will reject all SSL connections for staging
}
# production.com.conf - Keep as is but ensure proper SSL config
server {
listen 443 ssl;
server_name production.com;
# ... existing production SSL config ...
}
Here's the complete solution that properly handles both domains:
# staging.com.conf
server {
listen 80;
server_name staging.com;
# ... existing HTTP config ...
}
server {
listen 443 ssl;
server_name staging.com;
ssl_certificate /etc/nginx/ssl/staging.crt;
ssl_certificate_key /etc/nginx/ssl/staging.key;
return 444; # Connection closed without response
}
# production.com.conf
server {
listen 80;
server_name production.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name production.com;
ssl_certificate /etc/nginx/ssl/production.crt;
ssl_certificate_key /etc/nginx/ssl/production.key;
# ... rest of production config ...
}
1. Each domain needs its own SSL server block, even if you want to reject connections
2. The return 444 directive cleanly closes the connection for unwanted SSL access
3. Always include specific server_name directives for all SSL server blocks
4. Keep your default_server blocks as catch-alls for unmatched requests
After implementing these changes, verify with:
sudo nginx -t
sudo systemctl reload nginx
Then test both scenarios:
- https://staging.com should immediately close the connection
- https://production.com should serve the correct content