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

Flask HTTPS and Domain Checklist

If you're trying to validate domain and HTTPS setup for a Flask app in production, this guide shows you what to verify step-by-step. Use it to confirm DNS, Nginx, TLS, redirects, and certificate renewal are working before or after launch.

Quick Fix / Quick Setup

Run these checks first:

bash
dig +short yourdomain.com

dig +short www.yourdomain.com

sudo nginx -t && sudo systemctl reload nginx

sudo certbot certificates

curl -I http://yourdomain.com
curl -I https://yourdomain.com
curl -Iv https://yourdomain.com

Replace yourdomain.com with the production domain. This validates DNS resolution, Nginx config syntax, certificate presence, and HTTP/HTTPS response behavior.

What’s Happening

A working Flask HTTPS setup depends on four layers:

  1. DNS points to the correct server
  2. Nginx matches the correct server_name
  3. A valid certificate is installed for the active hostnames
  4. HTTP redirects cleanly to HTTPS

If any layer is wrong, users may see certificate warnings, the wrong site, redirect loops, timeouts, or plain HTTP.

Step-by-Step Guide

  1. Verify the domain resolves to the correct public IP
    bash
    dig +short yourdomain.com
    dig +short www.yourdomain.com
    

    Confirm both records point to the intended server or CDN endpoint.
  2. Confirm DNS record types are correct
    Use:
    • A for IPv4
    • AAAA for IPv6
    • CNAME only where appropriate

    Remove stale records that still point to old servers.
  3. Verify the firewall allows HTTP and HTTPS
    On Ubuntu with UFW:
    bash
    sudo ufw status
    

    Confirm ports 80 and 443 are allowed.
  4. Check that Nginx has the correct domain names
    Inspect the active site config and confirm:
    nginx
    server_name yourdomain.com www.yourdomain.com;
    

    Example HTTPS server block:
    nginx
    server {
        listen 443 ssl http2;
        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://unix:/run/gunicorn.sock;
            include proxy_params;
            proxy_redirect off;
        }
    }
    
  5. Validate Nginx configuration before reload
    bash
    sudo nginx -t
    

    Fix syntax errors, duplicate server_name entries, and invalid certificate paths before continuing.
  6. Reload Nginx after changes
    bash
    sudo systemctl reload nginx
    
  7. Confirm the HTTP virtual host works correctly
    bash
    curl -I http://yourdomain.com
    

    Expected result:
    • 301 or 308 redirect to HTTPS, or
    • temporary ACME challenge handling during certificate issuance

    Example redirect block:
    nginx
    server {
        listen 80;
        listen [::]:80;
        server_name yourdomain.com www.yourdomain.com;
    
        return 301 https://$host$request_uri;
    }
    
  8. Verify a certificate exists for the required hostnames
    bash
    sudo certbot certificates
    

    Confirm:
    • certificate name matches expected domain set
    • all required hostnames are listed
    • expiry date is valid
  9. Check certificate paths in Nginx
    Confirm ssl_certificate and ssl_certificate_key point to active files:
    nginx
    ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
    
  10. Test HTTPS directly
    bash
    curl -Iv https://yourdomain.com
    

    Confirm:
    • TLS negotiation succeeds
    • certificate hostname matches
    • certificate is not expired
    • response comes from the expected host
  11. Verify HTTP redirects to HTTPS consistently
    bash
    curl -I http://yourdomain.com
    curl -I http://www.yourdomain.com
    

    Confirm both hostnames either:
    • redirect to their HTTPS equivalent, or
    • canonicalize intentionally to one preferred hostname
  12. Verify HTTPS serves the correct site
    bash
    curl -I https://yourdomain.com
    curl -I https://www.yourdomain.com
    

    Confirm you are not hitting:
    • the default Nginx site
    • an old certificate
    • another application on the same server
  13. Confirm the Flask app works behind HTTPS
    Test a known route:
    bash
    curl -I https://yourdomain.com/health
    

    Expected result:
    • 200, 204, or intentional 301
    • not 500 or 502
  14. Validate certificate renewal
    Check timer or dry-run renewal:
    bash
    systemctl list-timers | grep certbot
    sudo certbot renew --dry-run
    
  15. Check HSTS only after HTTPS is stable
    If enabled, confirm the header is intentional:
    bash
    curl -I https://yourdomain.com | grep -i strict-transport-security
    

    Do not force HSTS until all intended hostnames and subdomains behave correctly over HTTPS.
  16. Confirm there is no mixed content
    Open the site in a browser and verify:
    • CSS loads over HTTPS
    • JS loads over HTTPS
    • images load over HTTPS
    • API calls use HTTPS
  17. If using a proxy or load balancer, verify forwarded headers
    Confirm TLS terminates where expected and headers are passed correctly.
    Typical Nginx proxy settings:
    nginx
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    
  18. Record final validation results
    Confirm these are all true:
    • working DNS
    • valid certificate
    • correct redirect behavior
    • renewal status verified
    • successful Flask response over HTTPS

Common Causes

  • DNS points to the wrong server → The domain resolves, but traffic reaches an old host or different service → Update A/AAAA/CNAME records and wait for propagation.
  • Nginx server_name does not match the requested domain → Nginx serves the default site or wrong virtual host → Correct server_name and reload Nginx.
  • Certificate does not cover all hostnames → Browsers show a mismatch warning on www or apex → Reissue the certificate including every required hostname.
  • Port 443 is blocked → HTTPS times out even though Nginx is configured → Open the firewall or cloud security group for 443.
  • HTTP-to-HTTPS redirect loop exists → Browser reports too many redirects → Fix redirect rules across Nginx, Flask, proxy headers, and CDN settings.
  • Old or duplicate Nginx site configs are active → Requests hit the wrong certificate or wrong app → Remove stale configs and validate with nginx -T.
  • Certbot renewal is not working → HTTPS works initially but fails after certificate expiry → Fix renewal timer, challenge routing, or certificate permissions.
  • Mixed-content assets remain on HTTP → Page loads with warnings or broken CSS/JS → Update asset URLs, proxy handling, and HTTPS-aware URL generation.

Debugging Section

Check these systems in order:

bash
dig +short yourdomain.com
dig +short www.yourdomain.com
dig @1.1.1.1 yourdomain.com
sudo ufw status
sudo nginx -t
sudo nginx -T
sudo systemctl status nginx
sudo systemctl reload nginx
sudo certbot certificates
sudo certbot renew --dry-run
sudo tail -n 100 /var/log/nginx/error.log
sudo tail -n 100 /var/log/nginx/access.log
sudo tail -n 100 /var/log/letsencrypt/letsencrypt.log
curl -I http://yourdomain.com
curl -I https://yourdomain.com
curl -Iv https://yourdomain.com
openssl s_client -connect yourdomain.com:443 -servername yourdomain.com

What to look for:

  • Nginx status
    • service is active (running)
  • Nginx error log
    • TLS errors
    • wrong certificate path
    • upstream failures
    • duplicate server_name
  • Nginx access log
    • request hostnames
    • 301, 308, 200, 404, 502
  • nginx -T
    • duplicate site definitions
    • wrong default server
    • stale configs still loaded
  • Let’s Encrypt log
    • ACME challenge failures
    • renewal permission issues
  • openssl s_client
    • served certificate CN/SAN values
    • full chain issues
    • expiry details

If requests reach Nginx but fail upstream, validate Gunicorn and Flask separately before treating it as an HTTPS issue. For upstream failures, see Flask HTTPS Not Working After Certbot.

Checklist

  • yourdomain.com resolves to the correct production IP
  • www.yourdomain.com resolves correctly or intentionally redirects
  • Ports 80 and 443 are open on the server and firewall
  • Nginx server_name matches the production domain names
  • sudo nginx -t passes without errors
  • HTTP requests return the expected redirect or challenge response
  • HTTPS certificate covers the active domain names
  • Certificate is not expired
  • Nginx certificate paths point to the current files
  • curl -Iv https://yourdomain.com completes TLS successfully
  • HTTP redirects to HTTPS for all intended hostnames
  • HTTPS serves the correct Flask app, not the default Nginx site
  • App routes return successful responses over HTTPS
  • Static assets load over HTTPS without mixed-content warnings
  • certbot renew --dry-run succeeds or renewal timer is present
  • Canonical redirect behavior between apex and www is intentional
  • No redirect loop exists between Nginx, Flask, CDN, or proxy layers

FAQ

Q: Should both apex and www use HTTPS?
A: Yes, if both are reachable. Either serve both with valid certificates or redirect one canonical host to the other over HTTPS.

Q: Can I force HTTPS before the certificate is working?
A: No. Confirm certificate issuance and HTTPS access first, then enforce redirects and HSTS.

Q: Why does HTTPS work for one hostname but not another?
A: The certificate, DNS, or Nginx server_name may not include the missing hostname.

Q: Why does Certbot succeed but the browser still shows the old certificate?
A: Nginx may not have been reloaded, the wrong server block may be active, or a proxy/CDN is serving a different cert.

Q: What should the first HTTPS test be?
A: Use curl -Iv https://yourdomain.com and inspect certificate validity, hostname match, and response headers.

Q: Should I enable HSTS immediately?
A: Only after HTTPS is fully working for all intended hostnames and redirects are stable.

Q: Do I need separate certificates for apex and www?
A: No. A single certificate can cover both if both names are included during issuance.

Q: Why does nginx -t pass but HTTPS still fail?
A: Syntax may be correct while DNS, firewall, certificate path, or certificate hostname coverage is still wrong.

Q: How do I confirm certificate renewal is safe?
A: Run sudo certbot renew --dry-run and confirm no challenge or permission errors occur.

Q: What is the minimum validation before launch?
A: Correct DNS, open ports 80/443, valid certificate, working HTTPS response, and clean HTTP-to-HTTPS redirect behavior.

Final Takeaway

A production-ready Flask HTTPS setup is valid only when DNS, Nginx, certificate coverage, redirect behavior, and renewal all work together. Use this checklist to verify each layer before launch and after every domain or TLS change.