Tutorial

Force HTTPS Everywhere: SSL Redirects, HSTS, and Mixed Content Fixes

March 30, 2026

Back to Blog

Introduction: Why HTTPS Is No Longer Optional

In 2026, HTTPS is not a nice-to-have — it is a baseline requirement for every website. Google Chrome labels all HTTP sites as "Not Secure" with a prominent red warning in the address bar. Google Search uses HTTPS as a ranking signal, giving secure sites a measurable SEO advantage. Modern web APIs like geolocation, service workers, and HTTP/2 require HTTPS. And most importantly, HTTPS protects your users from eavesdropping, data tampering, and man-in-the-middle attacks on every page of your site.

But simply having an SSL certificate is not enough. Your site needs to enforce HTTPS everywhere: redirect all HTTP traffic to HTTPS, enable HSTS headers to prevent downgrade attacks, and fix mixed content issues that can break your secure pages. This guide covers every step of the process, from basic redirects through HSTS preloading, with configurations for both Nginx and Apache.

The Cost of HTTP: A site served over HTTP allows any network intermediary (ISPs, public WiFi operators, attackers) to inject ads, modify content, steal session cookies, and intercept login credentials. This is not theoretical — it happens routinely on public WiFi networks and in some countries at the ISP level.

Step 1: HTTP to HTTPS Redirect

The first step is ensuring that every HTTP request is redirected to its HTTPS equivalent with a 301 Permanent Redirect. This tells browsers and search engines that the HTTPS version is the canonical URL.

Nginx Configuration

# Redirect all HTTP traffic to HTTPS
server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;

    # 301 permanent redirect to HTTPS
    return 301 https://$host$request_uri;
}

# HTTPS server block
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name example.com www.example.com;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    root /var/www/example.com/public_html;
    index index.html index.php;

    # ... rest of your configuration
}
Why 301 and not 302? A 301 (Permanent Redirect) tells browsers to cache the redirect and always go directly to HTTPS in the future. A 302 (Temporary Redirect) would cause browsers to check HTTP first on every visit, adding latency and leaving a window for interception. Always use 301 for HTTP-to-HTTPS redirects.

Apache Configuration

# .htaccess method (simplest)
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
# VirtualHost method (recommended)
<VirtualHost *:80>
    ServerName example.com
    ServerAlias www.example.com
    Redirect permanent / https://example.com/
</VirtualHost>

Step 2: HSTS (HTTP Strict Transport Security)

HSTS is a security header that tells browsers: "This site should only be accessed over HTTPS. If someone tries to load it over HTTP, do not even make the request — upgrade it to HTTPS automatically in the browser." This eliminates the brief HTTP request that 301 redirects still allow.

How HSTS Works

First visit:
HTTP request
301 redirect
to HTTPS
Browser receives
HSTS header
All future visits:
browser auto-upgrades
No HTTP request
ever sent again

HSTS Header Configuration

# Nginx — add to HTTPS server block
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
# Apache — add to HTTPS VirtualHost
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"

HSTS Directives Explained

DirectiveValueMeaning
max-age31536000Browser remembers the HSTS policy for 1 year (in seconds)
includeSubDomains-HSTS applies to all subdomains (api.example.com, blog.example.com, etc.)
preload-Signals that you want to be added to the browser HSTS preload list
HSTS is a Commitment: Once you enable HSTS with a long max-age, browsers will refuse to connect over HTTP even if you remove the header later (until the max-age expires). Start with a short max-age (300 seconds) for testing, then increase to 31536000 (1 year) once you are confident everything works.

Step 3: HSTS Preload List

The HSTS preload list is maintained by Google and bundled into all major browsers (Chrome, Firefox, Safari, Edge). Once your domain is on this list, browsers will always use HTTPS for your domain — even on the very first visit, before any HSTS header is received.

Requirements for Preload Submission

  • Serve a valid SSL certificate
  • Redirect all HTTP traffic to HTTPS on the same host
  • Serve the HSTS header on the base domain with max-age >= 31536000
  • Include the includeSubDomains directive
  • Include the preload directive
  • All subdomains must also support HTTPS

Submit your domain at hstspreload.org. The review process takes several weeks, and once approved, your domain will be hardcoded into browser binaries.

Preloading is Difficult to Reverse: Removing a domain from the preload list can take months because it requires a new browser release. Only submit to the preload list if you are absolutely certain your entire domain and all subdomains will always use HTTPS.

Step 4: Fix Mixed Content

Mixed content occurs when an HTTPS page loads resources (images, scripts, stylesheets, fonts) over plain HTTP. Browsers block or warn about mixed content because an attacker could modify these insecure resources to compromise the otherwise secure page.

Types of Mixed Content

Active Mixed Content Blocked

Scripts, stylesheets, iframes, and XMLHttpRequests loaded over HTTP. These can modify the page DOM and are blocked by all modern browsers.

<script src="http://cdn.example.com/app.js">

Passive Mixed Content Warning

Images, audio, video, and other media loaded over HTTP. These cannot modify the page but can be replaced by an attacker (e.g., swapping an image).

<img src="http://cdn.example.com/photo.jpg">

Finding Mixed Content

# Method 1: Chrome DevTools Console
# Open your site in Chrome, press F12, check the Console tab
Mixed Content: The page at 'https://example.com/' was loaded over HTTPS,
but requested an insecure resource 'http://example.com/image.jpg'.

# Method 2: Command-line search in your codebase
$ grep -rn 'http://' /var/www/example.com/ \
--include="*.html" --include="*.php" --include="*.css" --include="*.js" \
| grep -v 'https://'

Fixing Mixed Content

Current URLFixExample
http://example.com/style.cssChange to HTTPShttps://example.com/style.css
http://cdn.example.com/app.jsChange to HTTPShttps://cdn.example.com/app.js
http://example.com/image.jpgUse protocol-relative//example.com/image.jpg
Hardcoded in database (WordPress)Search and replaceWP-CLI: wp search-replace 'http://example.com' 'https://example.com'

Content-Security-Policy: upgrade-insecure-requests

As a safety net, you can add a CSP header that instructs browsers to automatically upgrade all HTTP resource requests to HTTPS:

# Nginx
add_header Content-Security-Policy "upgrade-insecure-requests" always;

# Apache
Header always set Content-Security-Policy "upgrade-insecure-requests"

# HTML meta tag (alternative)
<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">
This Is a Safety Net, Not a Fix: The upgrade-insecure-requests directive makes browsers silently upgrade HTTP URLs to HTTPS. This prevents mixed content warnings, but only if the HTTPS versions of those resources actually exist. Always fix the URLs in your code as well — do not rely solely on this header.

Step 5: Test Your HTTPS Configuration

Essential Testing Tools

SSL Labs (ssllabs.com)

Comprehensive SSL/TLS analysis. Tests cipher suites, protocol versions, certificate chain, HSTS, and known vulnerabilities. Aim for an A+ rating.

Security Headers (securityheaders.com)

Checks your security headers including HSTS, CSP, X-Frame-Options, X-Content-Type-Options, and Referrer-Policy. Aim for an A rating or better.

Why No Padlock (whynopadlock.com)

Scans a page for mixed content issues that prevent the padlock icon from appearing. Lists every insecure resource URL for easy fixing.

HSTS Preload Checker

Verify your HSTS configuration meets all preload requirements before submitting to hstspreload.org.

Common HTTPS Mistakes and How to Avoid Them

Redirect Loop with Cloudflare Flexible SSL

If Cloudflare's SSL mode is set to "Flexible," Cloudflare connects to your server over HTTP. If your server redirects HTTP to HTTPS, you get an infinite loop. Fix: Set Cloudflare SSL mode to "Full (Strict)" and install a certificate on your origin server.

HSTS with Short max-age

Setting max-age to 300 (5 minutes) in production provides almost no protection. After 5 minutes, the browser forgets the HSTS policy and will attempt HTTP again. Use 31536000 (1 year) for production.

Missing www Redirect

Users may visit http://www.example.com, http://example.com, https://www.example.com, or https://example.com. All four variations should redirect to a single canonical URL (typically https://example.com or https://www.example.com).

Expired SSL Certificate

An expired certificate is worse than no certificate — browsers show a scary full-page warning that most users will not click through. Set up auto-renewal with Let's Encrypt and monitor certificate expiry dates.

Complete Nginx HTTPS Configuration Template

# /etc/nginx/sites-available/example.com

# HTTP to HTTPS redirect
server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;
    return 301 https://example.com$request_uri;
}

# www to non-www redirect
server {
    listen 443 ssl http2;
    server_name www.example.com;
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    return 301 https://example.com$request_uri;
}

# Main HTTPS server
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name example.com;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    # Security headers
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Content-Security-Policy "upgrade-insecure-requests" always;

    root /var/www/example.com/public_html;
    index index.html index.php;
}

How Panelica Handles HTTPS

HTTPS by Default: Panelica enforces HTTPS automatically for every domain. When you create a domain, the system issues a Let's Encrypt certificate, configures HTTP-to-HTTPS redirect, and enables HSTS headers in the Nginx template — all without any manual configuration.
  • Automatic Let's Encrypt issuance — SSL certificates are issued during domain creation
  • Auto-renewal — certificates are renewed before expiry without intervention
  • HTTP-to-HTTPS redirect — pre-configured in the Nginx template for every domain
  • HSTS headers enabled — included in the default Nginx configuration
  • TLS 1.2+ enforced — older, insecure TLS versions are disabled by default

Conclusion

Enforcing HTTPS everywhere is a multi-step process: redirect HTTP to HTTPS with 301, enable HSTS to prevent downgrade attacks, fix mixed content to maintain browser trust, and optionally submit to the HSTS preload list for maximum protection. Each step builds on the previous one to create a comprehensive HTTPS enforcement strategy. With free certificates from Let's Encrypt and modern web server configurations, there is no longer any excuse for serving content over plain HTTP. Your users deserve encrypted connections on every page, and search engines will reward you for providing them.

Share: