HTTPS Everywhere: Why and How to Enforce It
Security First — Part 14 of 30
It's 3:00 AM on a Tuesday. Your phone is lighting up with PagerDuty alerts. "Connection Refused" is firing across your primary API gateway. Your site isn't down exactly — but no user can log in, and your payment processor is rejecting every transaction. Your DevOps team is combing through firewall rules and load balancer configs. Two hours and a frantic Slack thread later, someone finds the culprit: a 2KB text file ending in .pem.
An SSL certificate expired.
This scenario plays out constantly. According to industry data cited by certificate management firm expiring.at, the average cost of a certificate-related outage for a large company runs $300,000 to $500,000 per hour — and remediation typically takes 3 to 5 hours. Microsoft has had their own share of certificate-related embarrassments, including a notable Teams outage caused by an expired certificate that knocked the service offline for hours. Even Spotify's Megaphone podcast platform went dark for 8 hours due to a missed certificate renewal, disrupting ad revenue for thousands of podcasters.
But certificate expiry is just one piece of the HTTPS story. If you're building with AI coding tools and you haven't thought carefully about HTTPS enforcement, you may have a much quieter problem that doesn't announce itself with a 3 AM alert — one where attackers are silently reading your users' passwords right now.
What HTTPS Actually Does (And What It Doesn't)
HTTPS is HTTP wrapped in TLS (Transport Layer Security). It does three things:
- Encrypts the data between the browser and your server, so a network attacker can't read it
- Authenticates that the server you're connecting to is actually who it claims to be (via the certificate)
- Ensures integrity — the data hasn't been tampered with in transit
HTTP does none of these things. Every byte you send over plain HTTP is readable by anyone on the same network — your ISP, a hotel Wi-Fi router, anyone running a packet sniffer at a coffee shop.
As of 2025, Mozilla research tracking over 140 million Firefox users found that over 80% of web pages globally load over HTTPS, up from under 30% in 2014. The 2025 Web Almanac by HTTP Archive puts the current figure even higher — over 98.8% of all requests use HTTPS on mobile. That sounds great.
Here's the problem: having HTTPS and enforcing HTTPS are completely different things.
The Three Ways HTTPS Enforcement Fails
1. The HTTP Backdoor
Your site might be perfectly accessible over HTTPS, but if it also responds to plain HTTP requests — even to serve a redirect — you have a window of vulnerability. Here's why.
When a user types yourapp.com into their browser (without https://), the browser first tries HTTP. If you're relying on a server-side redirect to bounce them to HTTPS, an attacker who controls the network (at a coffee shop, on a corporate proxy, on public Wi-Fi) can intercept that very first HTTP request before the redirect ever happens.
This attack is called SSL stripping or an HTTP downgrade attack. The attacker sits between the user and your server, quietly converts your HTTPS site to HTTP, and relays traffic in plaintext — reading passwords, session tokens, anything. The user sees the site working normally. There's no obvious warning. According to JumpCloud's security research, attackers frequently exploit weak or misconfigured HTTP-to-HTTPS redirects to keep sessions on insecure HTTP.
2. Mixed Content
Even if your main page loads over HTTPS, if any resource on that page — a JavaScript file, an image, a stylesheet, an API call — loads over plain HTTP, you have what's called mixed content. This defeats the purpose of HTTPS for active content entirely.
According to The Hacker News, 53.5% of websites have weak SSL setups that leave their attack surfaces exposed. Mixed content is one of the most common culprits. An attacker who can modify that HTTP-loaded JavaScript file can inject arbitrary code into your "secure" page.
AI coding tools are particularly prone to generating mixed content. If your AI assistant is pulling in a library from an old CDN URL starting with http://, or generating hardcoded API endpoint calls to http:// paths, you've got a problem.
3. Certificate Mismanagement
Your certificate is the cryptographic proof that you are who you say you are. If it expires, browsers will slam the door on your users with a hard error — no way to click through. If it's misconfigured (wrong domain, weak cipher suites, outdated TLS version), you're either vulnerable or broken.
A 2025 vulnerability disclosure, CVE-2025-3200, specifically flagged Com-Server systems still running TLS 1.0 and TLS 1.1 — protocols deprecated years ago — as vulnerable to information disclosure through protocol downgrade attacks. Modern standards require TLS 1.2 minimum, with TLS 1.3 preferred.
Making things more urgent: in April 2025, the CA/Browser Forum approved a ballot to reduce maximum certificate validity to just 47 days by 2029, with the first reduction to 200 days beginning March 15, 2026. Manual certificate management is quickly becoming impossible at scale. Automation is no longer optional.
How to Enforce HTTPS Properly
Let's go step by step through the full enforcement stack.
Step 1: Get a Certificate (and Automate Renewal)
If you don't have a certificate yet, Let's Encrypt provides free, trusted certificates. The certbot tool handles both issuance and automated renewal:
# Install certbot (Ubuntu/Debian)
sudo apt install certbot python3-certbot-nginx
# Issue a certificate and auto-configure nginx
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
# Test automatic renewal
sudo certbot renew --dry-run
# Verify the renewal timer is active
sudo systemctl status certbot.timer
Certbot sets up a cron job or systemd timer to renew your certificate automatically before it expires. Verify it's running — don't assume. The engineer who set it up might have left the company, the timer might have silently failed, or the renewal email might be going to a deactivated inbox.
Set up monitoring separately. Add your domain to a free certificate monitoring service (e.g., SSL Labs, UptimeRobot) that alerts you by email when your cert is within 30 days of expiry. Belt and suspenders.
Step 2: Force HTTPS Redirects at the Server Level
Nginx:
# Force all HTTP traffic to HTTPS
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
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;
# Only allow modern TLS versions
ssl_protocols TLSv1.2 TLSv1.3;
# ... rest of your config
}
Apache:
<VirtualHost *:80>
ServerName yourdomain.com
Redirect permanent / https://yourdomain.com/
</VirtualHost>
Node.js / Express (if you're handling TLS at the app layer):
const express = require('express');
const app = express();
// Force HTTPS in production
app.use((req, res, next) => {
if (process.env.NODE_ENV === 'production' && !req.secure) {
return res.redirect(301, `https://${req.headers.host}${req.url}`);
}
next();
});
Python / Flask:
from flask import Flask, redirect, request
app = Flask(__name__)
@app.before_request
def enforce_https():
if not request.is_secure and app.env == 'production':
url = request.url.replace('http://', 'https://', 1)
return redirect(url, code=301)
Step 3: Add HTTP Strict Transport Security (HSTS)
HTTP redirects are better than nothing, but they still require that first vulnerable HTTP request. HSTS eliminates it entirely. Once a browser sees your HSTS header, it will never attempt HTTP to your domain again — it goes straight to HTTPS, even before making a network connection.
# In your HTTPS server block
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
# Flask example
from flask import Flask
from flask_talisman import Talisman
app = Flask(__name__)
Talisman(app, strict_transport_security=True,
strict_transport_security_max_age=31536000,
strict_transport_security_include_subdomains=True,
strict_transport_security_preload=True)
// Express with helmet
const helmet = require('helmet');
app.use(helmet.hsts({
maxAge: 31536000, // 1 year in seconds
includeSubDomains: true,
preload: true
}));
The preload directive means you can submit your domain to the HSTS Preload List — a list baked into Chrome, Firefox, Safari, and Edge. Browsers that have this list will never attempt HTTP to your domain, even on a user's very first visit. This defeats SSL stripping completely.
A word of caution from Let's Encrypt's own integration guide: once you have HSTS active, certificate errors become hard failures. Users cannot click through a warning — the browser simply refuses to connect. Make absolutely sure your certificate is valid and auto-renewing before setting max-age to a long value. Start with max-age=86400 (one day) while you're testing, then ramp up to a year once you're confident.
Step 4: Fix Mixed Content
Audit your pages for HTTP resources using Chrome DevTools:
- Open Chrome DevTools → Console tab
- Look for warnings like:
Mixed Content: The page was loaded over HTTPS, but requested an insecure resource - Also check the Network tab, filtering for
http://requests
You can also add a Content Security Policy header to auto-upgrade insecure requests:
# In nginx — upgrade any remaining HTTP subresources to HTTPS automatically
add_header Content-Security-Policy "upgrade-insecure-requests;" always;
<!-- Or add this meta tag to your HTML <head> -->
<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">
For AI-generated code, always grep your codebase for hardcoded http:// URLs:
# Find hardcoded HTTP URLs in your source code
grep -rn 'http://' ./src --include='*.js' --include='*.ts' --include='*.py' --include='*.html'
# Most should be https:// — review each hit
Step 5: Verify Your TLS Configuration
Run your domain through SSL Labs' Server Test — it's free, takes two minutes, and gives you a letter grade (A+ to F) with specific recommendations. Target an A+.
Key things it checks:
- TLS version support (you want TLS 1.2 and 1.3 only — not 1.0 or 1.1)
- Cipher suite strength
- Certificate validity and chain
- HSTS header presence
- Forward secrecy support
From the command line:
# Quick TLS check using openssl
openssl s_client -connect yourdomain.com:443 -brief
# Check which TLS versions your server supports
nmap --script ssl-enum-ciphers -p 443 yourdomain.com
Where AI Coding Tools Can Lead You Astray
When you ask an AI assistant to spin up a web server or API, the output often works locally but skips security configuration entirely. Common pitfalls:
- Local dev configs leak into production. That Flask development server running on port 5000 over HTTP? Great for local testing, not for production.
- AI-generated Docker setups often expose HTTP ports directly. Check your
docker-compose.ymlfor exposed port 80 without a TLS proxy in front. - Hardcoded API endpoints often use
http://. Generated code frequently calls third-party services with HTTP URLs — always review. - Certificate paths get hardcoded. If a certbot renewal moves your cert files, a hardcoded path in your config breaks silently.
Always ask your AI assistant explicitly: "Show me how to configure HTTPS enforcement, HSTS headers, and certificate auto-renewal for this stack." Don't assume it's included.
Checklist: HTTPS Enforcement
- Certificate issued via Let's Encrypt (or another CA) for your domain and all subdomains
- Auto-renewal configured with certbot and verified with
sudo certbot renew --dry-run - External certificate monitoring set up to alert you 30+ days before expiry
- HTTP → HTTPS redirect configured at the server level (nginx, Apache, or load balancer)
- HSTS header added with
max-age=31536000; includeSubDomains - HSTS preload submitted at hstspreload.org once you're confident
- Mixed content audited using Chrome DevTools and
grepforhttp://in source -
upgrade-insecure-requestsCSP header added as a safety net - SSL Labs A+ grade achieved at ssllabs.com/ssltest
- TLS 1.0 and 1.1 disabled — only TLS 1.2 and TLS 1.3 allowed
- No hardcoded
http://URLs in AI-generated code (run the grep command above) - Production HTTPS config separate from local dev config — never run plain HTTP in prod
Ask The Guild
Community prompt: What's the most painful HTTPS or certificate incident you've experienced in a project — whether a forgotten renewal, a mixed content rabbit hole, or a misconfigured redirect that went unnoticed for weeks? Share your war story (and the fix) so the rest of us can learn from it. Drop it in the #security-first channel.
Tom Hundley is a software architect with 25 years of experience. He runs the Security First track at the AI Coding Guild, helping vibe coders build things that don't come apart at the seams.
Sources:
- Mozilla Research: The State of HTTPS Adoption on the Web (2025)
- HTTP Archive: 2025 Web Almanac — Security
- The Hacker News: How SSL Misconfigurations Impact Your Attack Surface (2025)
- expiring.at: The Million-Dollar Expiration — Certificate Outages (2026)
- World Wide Technology: SSL Certificate Validity Changes 2025
- SentinelOne: CVE-2025-3200 TLS Protocol Vulnerability
- JumpCloud: What Is an HTTP Downgrade Attack? (2025)
- Let's Encrypt: Integration Guide