Flask HTTPS Not Working After Certbot
If HTTPS stopped working after Certbot or your Flask site is still not serving securely, this guide shows you how to diagnose and fix the Nginx, certificate, and redirect setup step-by-step. The goal is to restore a valid TLS configuration and confirm that Flask is reachable over HTTPS.
Quick Fix / Quick Setup
Run the core checks first:
sudo nginx -t && sudo systemctl reload nginx
sudo certbot certificates
sudo ls -l /etc/letsencrypt/live/yourdomain.com/
sudo grep -R "ssl_certificate\|server_name\|listen 443" /etc/nginx/sites-enabled /etc/nginx/conf.d
curl -I http://yourdomain.com
curl -Ik https://yourdomain.com
openssl s_client -connect yourdomain.com:443 -servername yourdomain.com </dev/null | openssl x509 -noout -subject -issuer -dates
In most cases, HTTPS fails because Nginx is not loading the Certbot-managed server block, the wrong domain is configured in server_name, port 443 is not active, or Nginx is pointing to an invalid certificate path.
What’s Happening
Certbot usually installs or updates Nginx TLS settings, but HTTPS still depends on a valid Nginx server block for port 443, correct server_name values, and certificate files that exist and match the requested domain.
If Nginx loads the wrong virtual host, uses stale certificate paths, or cannot bind to 443, the browser will show certificate, redirect, or connection errors even if Certbot completed successfully.
Step-by-Step Guide
- Confirm DNS points to the correct server.bash
dig +short yourdomain.com dig +short www.yourdomain.com
Verify the returned IP matches your VPS or load balancer. - Check that Nginx is valid and running.bash
sudo nginx -t sudo systemctl status nginx --no-pager
Fix syntax errors before continuing. - Inspect installed certificates.bash
sudo certbot certificates
Verify the domain names listed and note the certificate paths under/etc/letsencrypt/live/.../fullchain.pemandprivkey.pem. - Verify the Nginx HTTPS server block exists and matches the domain.bash
sudo grep -R "server_name\|listen 443\|ssl_certificate\|ssl_certificate_key" /etc/nginx/sites-enabled /etc/nginx/conf.d
Ensure the correct server block includes:listen 443 ssl;orlisten 443 ssl http2;- the expected domain names in
server_name - valid
ssl_certificateandssl_certificate_keypaths
- Open the Nginx site config and correct it if needed.
Example working configuration:nginxserver { listen 80; server_name yourdomain.com www.yourdomain.com; return 301 https://$host$request_uri; } server { listen 443 ssl http2; server_name yourdomain.com www.yourdomain.com; ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem; location / { proxy_pass http://127.0.0.1:8000; include proxy_params; } }
If you are using a Unix socket, use:nginxlocation / { include proxy_params; proxy_pass http://unix:/run/gunicorn.sock; } - Test the config and reload Nginx.bash
sudo nginx -t sudo systemctl reload nginx
If reload fails, fix the exact file and line shown in the error output. - Verify port 443 is listening.bash
sudo ss -ltnp | grep :443
If nothing is listening on443, Nginx is not binding successfully or another service is conflicting. - Check the active certificate being served.bash
openssl s_client -connect yourdomain.com:443 -servername yourdomain.com </dev/null | openssl x509 -noout -subject -issuer -dates
Confirm:- subject matches the expected domain
- issuer is the expected CA
- certificate is not expired
- Test HTTP to HTTPS redirection.bash
curl -I http://yourdomain.com
Expect301or308to the HTTPS URL. - Test the HTTPS response directly.bash
curl -Ik https://yourdomain.com
Expect a valid HTTP response such as200,301, or your application response. A TLS handshake failure indicates certificate, vhost, or listener problems. - If Certbot changed files unexpectedly, re-run installation for Nginx.bash
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
Review the proposed changes before accepting them. - Validate Flask upstream after HTTPS is fixed.
If HTTPS loads but the app returns502or500, test Gunicorn separately:bashcurl -I http://127.0.0.1:8000
Or validate your socket-based upstream permissions and service state. If the proxy target is failing, continue with Fix: Nginx Not Connecting to Gunicorn (Connection Refused).
Common Causes
- Wrong
server_namein Nginx -> Nginx serves another virtual host or default certificate -> set exact domain names and reload Nginx. - Invalid certificate path -> Nginx points to a deleted, renamed, or wrong
/etc/letsencrypt/live/...directory -> updatessl_certificateandssl_certificate_keyto the active certificate path. - Port
443not configured -> Certbot completed but no HTTPS server block is active -> addlisten 443 ssl;and reload Nginx. - DNS still points elsewhere -> browser reaches another server with another certificate -> update A/AAAA records and wait for propagation.
- Default Nginx site takes precedence -> requests hit the wrong server block -> disable the default site or set the correct server block as the match for the domain.
- Certbot issued for a different hostname -> certificate does not match the requested domain -> reissue with the exact
-dvalues you use publicly. - Firewall blocks
443-> HTTP works but HTTPS times out -> allow HTTPS in UFW, cloud firewall, or load balancer rules. - Broken redirect logic -> HTTP redirects to invalid host, loops, or forces malformed URLs -> simplify the port
80block to a direct HTTPS redirect. - Nginx reload failed after Certbot edits -> old config remains active -> run
nginx -t, fix the error, and reload again. - IPv6 misconfiguration -> AAAA record points to a server that is not configured for TLS -> fix IPv6 Nginx binding or remove the AAAA record.
If Nginx fails validation after editing TLS directives, use Flask Nginx Config Test Failed (Fix Guide).
Debugging Section
Check the core logs and active configuration.
Nginx error log
sudo tail -n 100 /var/log/nginx/error.log
Look for:
- certificate file errors
- bind failures on
443 - syntax or include errors
- upstream errors after TLS succeeds
Nginx access log
sudo tail -n 100 /var/log/nginx/access.log
Look for:
- whether requests are reaching the expected host
- response codes for HTTP and HTTPS
- requests hitting the wrong domain or server block
Certbot log
sudo tail -n 100 /var/log/letsencrypt/letsencrypt.log
Look for:
- issuance failures
- renewal failures
- install-hook problems
- challenge validation errors
Service status
sudo systemctl status nginx --no-pager
sudo journalctl -u nginx -n 100 --no-pager
Look for failed reloads, startup errors, and permission problems.
Full loaded Nginx config
sudo nginx -T | less
Search for:
- duplicate
server_name - duplicate
listen 443 - stale certificate paths
- default site catching the request first
Open ports
sudo ss -ltnp | egrep ':80|:443'
Confirm Nginx is listening on both 80 and 443.
DNS resolution
dig +short yourdomain.com
dig +short www.yourdomain.com
dig +short AAAA yourdomain.com
Verify both IPv4 and IPv6 records point where expected.
TLS handshake and SNI behavior
openssl s_client -connect yourdomain.com:443 -servername yourdomain.com
Check:
- certificate chain
- negotiated protocol
- verification failures
- whether the certificate matches the hostname
Upstream test
If HTTPS works but Flask does not load:
curl -I http://127.0.0.1:8000
Or inspect Unix socket permissions and Gunicorn service state.
For the base production layout, see Deploy Flask with Nginx + Gunicorn (Step-by-Step Guide).
Checklist
- DNS for the domain points to the correct production server.
- Nginx config passes
nginx -twithout errors. - A server block with
listen 443 sslexists for the correct domain. -
ssl_certificateandssl_certificate_keypoint to valid Let’s Encrypt files. - Nginx is listening on port
443. - HTTP redirects to HTTPS correctly.
-
curl -Ik https://yourdomain.comreturns a valid response. - The certificate served matches the requested hostname.
- Firewall or security group allows inbound
443. - Flask/Gunicorn upstream is reachable after TLS termination.
For full production validation, use Flask Production Checklist (Everything You Must Do).
Related Guides
- Deploy Flask with Nginx + Gunicorn (Step-by-Step Guide)
- Flask Nginx Config Test Failed (Fix Guide)
- Fix: Nginx Not Connecting to Gunicorn (Connection Refused)
- Flask Production Checklist (Everything You Must Do)
FAQ
Q: Certbot completed successfully, so why is HTTPS still broken?
A: Certbot can issue a valid certificate while Nginx still serves the wrong vhost, wrong certificate path, or an inactive 443 config.
Q: Should Flask or Gunicorn serve HTTPS directly?
A: No. In a standard production setup, Nginx handles TLS and proxies to Gunicorn over localhost or a Unix socket.
Q: Why am I seeing the wrong certificate in the browser?
A: Your request is likely hitting a different Nginx server block, a different server entirely, or a hostname not included in the certificate.
Q: Why does HTTP work but HTTPS times out?
A: Port 443 is usually blocked, not listening, or not configured in Nginx.
Q: Can IPv6 break HTTPS after Certbot?
A: Yes. If your AAAA record points to a host without the same HTTPS configuration, some clients will fail while others work over IPv4.
Final Takeaway
When HTTPS fails after Certbot, the issue is usually not certificate issuance itself but the active Nginx configuration: wrong server block, wrong certificate path, port 443 not listening, or DNS mismatch. Validate the Nginx config, the served certificate, and the upstream path in that order.