HTTPS and access
Redirect HTTP to HTTPS
Keep the port 80 server small and redirect each valid hostname to its HTTPS equivalent without reflecting arbitrary hosts.
8 minute lesson
Once your site serves HTTPS, the HTTP listener on port 80 doesn’t disappear. It stays for two jobs: redirecting humans who typed the plain URL, and answering the HTTP-based certificate validation challenges your ACME client uses for renewals.
Keep that server block small and boring:
server {
listen 80;
server_name www.example.com example.com;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://www.example.com$request_uri;
}
}
Two details carry the weight here. The ACME challenge location comes first as a real file path, so certificate renewals keep working over plain HTTP even though everything else redirects. And the redirect target is a known canonical hostname, written out literally.
Why not https://$host$request_uri? Because $host comes from the client’s Host header, and building a redirect from unvalidated client input means anyone can make your server issue redirects to a name you never intended. This block only matches your declared server_name values anyway, so hardcoding the canonical name costs nothing and removes the question. Keep unrelated traffic out entirely with a separate default server:
server {
listen 80 default_server;
server_name _;
return 444;
}
Requests carrying random or hostile Host values hit this block and get the connection closed, instead of bouncing through your redirect.
Test all three cases
curl -I http://www.example.com/pricing
# HTTP/1.1 301 Moved Permanently
# Location: https://www.example.com/pricing
curl -I http://example.com/pricing
# Location: https://www.example.com/pricing <- apex funnels to canonical
curl -I http://203.0.113.10/ -H "Host: evil.example.net"
# curl: (52) Empty reply from server <- default server, no redirect
Confirm each response reaches the intended boundary: canonical host redirects to itself, the alternate host funnels to the canonical one, and an unknown Host value gets nothing useful.
The pitfall that bites in production is redirecting the ACME path along with everything else. Renewals then fail weeks later with a challenge error, long after the config change that caused it, and the certificate quietly expires. After adding or reshuffling redirects, verify the challenge path still returns 404 from the file root rather than a 301:
curl -I http://www.example.com/.well-known/acme-challenge/test
# HTTP/1.1 404 Not Found <- served from disk, not redirectedLesson completed