Reference Guide Detailed deployment notes with production context and concrete examples.

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:

bash
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

  1. 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.
  2. Check that Nginx is valid and running.
    bash
    sudo nginx -t
    sudo systemctl status nginx --no-pager
    

    Fix syntax errors before continuing.
  3. Inspect installed certificates.
    bash
    sudo certbot certificates
    

    Verify the domain names listed and note the certificate paths under /etc/letsencrypt/live/.../fullchain.pem and privkey.pem.
  4. 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; or listen 443 ssl http2;
    • the expected domain names in server_name
    • valid ssl_certificate and ssl_certificate_key paths
  5. Open the Nginx site config and correct it if needed.
    Example working configuration:
    nginx
    server {
        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:
    nginx
    location / {
        include proxy_params;
        proxy_pass http://unix:/run/gunicorn.sock;
    }
    
  6. 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.
  7. Verify port 443 is listening.
    bash
    sudo ss -ltnp | grep :443
    

    If nothing is listening on 443, Nginx is not binding successfully or another service is conflicting.
  8. 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
  9. Test HTTP to HTTPS redirection.
    bash
    curl -I http://yourdomain.com
    

    Expect 301 or 308 to the HTTPS URL.
  10. Test the HTTPS response directly.
    bash
    curl -Ik https://yourdomain.com
    

    Expect a valid HTTP response such as 200, 301, or your application response. A TLS handshake failure indicates certificate, vhost, or listener problems.
  11. 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.
  12. Validate Flask upstream after HTTPS is fixed.
    If HTTPS loads but the app returns 502 or 500, test Gunicorn separately:
    bash
    curl -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_name in 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 -> update ssl_certificate and ssl_certificate_key to the active certificate path.
  • Port 443 not configured -> Certbot completed but no HTTPS server block is active -> add listen 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 -d values 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 80 block 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

bash
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

bash
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

bash
sudo tail -n 100 /var/log/letsencrypt/letsencrypt.log

Look for:

  • issuance failures
  • renewal failures
  • install-hook problems
  • challenge validation errors

Service status

bash
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

bash
sudo nginx -T | less

Search for:

  • duplicate server_name
  • duplicate listen 443
  • stale certificate paths
  • default site catching the request first

Open ports

bash
sudo ss -ltnp | egrep ':80|:443'

Confirm Nginx is listening on both 80 and 443.

DNS resolution

bash
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

bash
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:

bash
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 -t without errors.
  • A server block with listen 443 ssl exists for the correct domain.
  • ssl_certificate and ssl_certificate_key point to valid Let’s Encrypt files.
  • Nginx is listening on port 443.
  • HTTP redirects to HTTPS correctly.
  • curl -Ik https://yourdomain.com returns 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).

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.