When deploying Nginx on Debian, the default configuration in /etc/nginx/sites-available/default
creates a potential security exposure. The current behavior where IP address access falls through to your production domain creates multiple issues:
- Security warnings from SSL testers
- Potential duplicate content SEO penalties
- Improper SSL certificate handling
For production environments, we should create an explicit catch-all server block that rejects invalid requests:
# /etc/nginx/sites-available/default
server {
listen 80 default_server;
listen [::]:80 default_server;
listen 443 ssl default_server;
listen [::]:443 ssl default_server;
ssl_certificate /path/to/dummy.crt;
ssl_certificate_key /path/to/dummy.key;
server_name _;
return 444; # Close connection without response
}
Your production domain should have its own dedicated configuration without default_server designation:
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name example.com www.example.com;
include snippets/ssl-example.com.conf;
include snippets/ssl-params.conf;
# Rest of your production configuration
...
}
The solution achieves several security benefits:
- Prevents IP-based access to your production site
- Eliminates SSL warnings for direct IP access
- Maintains full SSL security for your actual domain
- Properly handles HTTP to HTTPS redirects
For the default_server SSL configuration, you'll need a self-signed certificate:
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout /etc/nginx/ssl/dummy.key \
-out /etc/nginx/ssl/dummy.crt \
-subj "/CN=invalid"
After implementing these changes, verify with:
nginx -t # Test configuration
systemctl reload nginx # Apply changes
curl -I http://your_server_ip # Should show closed connection
curl -I https://your_server_ip # Should show closed connection
curl -I http://example.com # Should show 301 redirect
curl -I https://example.com # Should show 200 OK
When setting up Nginx on Debian, you'll encounter the default configuration in /etc/nginx/sites-available/default
that serves the Nginx welcome page. This becomes problematic when you need to properly handle IP-based access and implement proper SSL termination.
The current configuration has these characteristics:
# Default server block (problematic)
server {
listen 80 default_server;
listen [::]:80 default_server;
# This catches all unmatched domain requests
server_name _;
root /var/www/html;
index index.html;
location / {
return 403; # Forbidden access
}
}
For your production domain (example.com), you should implement this structure:
# HTTP to HTTPS redirect
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
# SSL Termination
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com www.example.com;
# SSL configuration
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
# Your actual application configuration
root /var/www/example.com;
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
# PHP processing
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
}
}
To properly block direct IP access while maintaining SSL functionality:
# Block all HTTP IP access
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
return 444; # No response (connection closed)
}
# Block all HTTPS IP access
server {
listen 443 ssl default_server;
listen [::]:443 ssl default_server;
server_name _;
# Use a self-signed cert for the default server
ssl_certificate /etc/nginx/ssl/dummy.crt;
ssl_certificate_key /etc/nginx/ssl/dummy.key;
return 444;
}
For the IP-blocking SSL server, create a self-signed certificate:
sudo mkdir -p /etc/nginx/ssl
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout /etc/nginx/ssl/dummy.key \
-out /etc/nginx/ssl/dummy.crt \
-subj "/CN=localhost"
After implementing these changes:
- Test SSL configuration:
sudo nginx -t
- Reload Nginx:
sudo systemctl reload nginx
- Verify behavior:
- HTTP IP access should immediately close connection
- HTTPS IP access should immediately close connection
- HTTP domain access should redirect to HTTPS
- HTTPS domain access should work normally
- Re-run SSL Labs test to confirm A+ rating
Key takeaways for production deployment:
- Always specify
default_server
explicitly - Use separate server blocks for HTTP and HTTPS
- Block IP access at both protocol levels
- Maintain proper SSL configuration for your domain
- Regularly test your SSL configuration with tools like SSL Labs