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:
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:
- DNS points to the correct server
- Nginx matches the correct
server_name - A valid certificate is installed for the active hostnames
- 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
- Verify the domain resolves to the correct public IPbash
dig +short yourdomain.com dig +short www.yourdomain.com
Confirm both records point to the intended server or CDN endpoint. - Confirm DNS record types are correct
Use:Afor IPv4AAAAfor IPv6CNAMEonly where appropriate
Remove stale records that still point to old servers. - Verify the firewall allows HTTP and HTTPS
On Ubuntu with UFW:bashsudo ufw status
Confirm ports80and443are allowed. - Check that Nginx has the correct domain names
Inspect the active site config and confirm:nginxserver_name yourdomain.com www.yourdomain.com;
Example HTTPS server block:nginxserver { 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; } } - Validate Nginx configuration before reloadbash
sudo nginx -t
Fix syntax errors, duplicateserver_nameentries, and invalid certificate paths before continuing. - Reload Nginx after changesbash
sudo systemctl reload nginx - Confirm the HTTP virtual host works correctlybash
curl -I http://yourdomain.com
Expected result:301or308redirect to HTTPS, or- temporary ACME challenge handling during certificate issuance
Example redirect block:nginxserver { listen 80; listen [::]:80; server_name yourdomain.com www.yourdomain.com; return 301 https://$host$request_uri; } - Verify a certificate exists for the required hostnamesbash
sudo certbot certificates
Confirm:- certificate name matches expected domain set
- all required hostnames are listed
- expiry date is valid
- Check certificate paths in Nginx
Confirmssl_certificateandssl_certificate_keypoint to active files:nginxssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem; - Test HTTPS directlybash
curl -Iv https://yourdomain.com
Confirm:- TLS negotiation succeeds
- certificate hostname matches
- certificate is not expired
- response comes from the expected host
- Verify HTTP redirects to HTTPS consistentlybash
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
- Verify HTTPS serves the correct sitebash
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
- Confirm the Flask app works behind HTTPS
Test a known route:bashcurl -I https://yourdomain.com/health
Expected result:200,204, or intentional301- not
500or502
- Validate certificate renewal
Check timer or dry-run renewal:bashsystemctl list-timers | grep certbot sudo certbot renew --dry-run - Check HSTS only after HTTPS is stable
If enabled, confirm the header is intentional:bashcurl -I https://yourdomain.com | grep -i strict-transport-security
Do not force HSTS until all intended hostnames and subdomains behave correctly over HTTPS. - 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
- If using a proxy or load balancer, verify forwarded headers
Confirm TLS terminates where expected and headers are passed correctly.
Typical Nginx proxy settings:nginxproxy_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; - 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/CNAMErecords and wait for propagation. - Nginx
server_namedoes not match the requested domain → Nginx serves the default site or wrong virtual host → Correctserver_nameand reload Nginx. - Certificate does not cover all hostnames → Browsers show a mismatch warning on
wwwor 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:
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)
- service is
- 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.comresolves to the correct production IP -
www.yourdomain.comresolves correctly or intentionally redirects - Ports
80and443are open on the server and firewall - Nginx
server_namematches the production domain names -
sudo nginx -tpasses 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.comcompletes 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-runsucceeds or renewal timer is present - Canonical redirect behavior between apex and
wwwis intentional - No redirect loop exists between Nginx, Flask, CDN, or proxy layers
Related Guides
- Deploy Flask with Nginx + Gunicorn (Step-by-Step Guide)
- Flask Domain and DNS Setup for Production
- Flask HTTPS Not Working After Certbot
- Flask Production Checklist (Everything You Must Do)
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.