Web servers
How to redirect HTTP to HTTPS
Redirect HTTP to HTTPS with one permanent (301 or 308) redirect on the port-80 listener, from each http:// URL to the matching https:// URL. Set up this way, it causes no loops behind proxies and does not break certificate renewal. The examples cover Nginx, Apache httpd 2.4, Caddy 2, IIS 10 and CDNs such as Cloudflare.
Prerequisites
HTTPS must already work before you force it.
- A valid certificate installed and
https://example.comloading without warnings (check with the SSL checker). - Access to the web server or CDN configuration.
- Pages free of hard-coded
http://resources; the mixed content scanner finds them.
Step 1: Nginx
Give port 80 its own server block that does nothing but redirect:
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
return 301 https://example.com$request_uri;
}
Using a fixed host collapses www and the apex in one hop; use https://$host$request_uri to keep the requested host. Then sudo nginx -t && sudo systemctl reload nginx.
Step 2: Apache
Apache redirects with Redirect permanent in the port-80 virtual host:
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
Redirect permanent / https://example.com/
</VirtualHost>
If you can only use .htaccess (shared hosting), use mod_rewrite:
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
Step 3: Caddy and IIS
Caddy redirects HTTP to HTTPS automatically for every site with a domain name; no configuration is needed. On IIS, install the URL Rewrite module and add this rule to web.config:
<rule name="HTTP to HTTPS" stopProcessing="true">
<match url="(.*)" />
<conditions>
<add input="{HTTPS}" pattern="^OFF$" />
</conditions>
<action type="Redirect" url="https://{HTTP_HOST}/{R:1}" redirectType="Permanent" />
</rule>
Step 4: Behind a CDN or load balancer
Redirect at the edge, or only when the proxy reports HTTP. If the CDN connects to the origin over plain HTTP, the origin sees every request as HTTP and an origin-side redirect loops. Either enable the redirect at the edge (Cloudflare Always Use HTTPS, or the equivalent "force HTTPS" switch of your CDN or load balancer) and remove it from the origin, or redirect only when the proxy says the client used HTTP:
if ($http_x_forwarded_proto = "http") {
return 301 https://$host$request_uri;
}
Better still, make the CDN connect to the origin over HTTPS with certificate validation (Cloudflare's Full (strict) mode).
Verify
Each http:// entry point should return a single 301 or 308 redirect to the final HTTPS URL. Check every entry point:
curl -sI http://example.com | grep -iE '^(HTTP|location)'
curl -sI http://www.example.com | grep -iE '^(HTTP|location)'
curl -sIL http://www.example.com | grep -iE '^(HTTP|location)'
Then confirm the HTTPS endpoint itself with the SSL checker, or from any terminal:
curl ssl.sh/example.com
Automatic renewal
The redirect does not interfere with Let's Encrypt renewals: HTTP-01 validation follows up to 10 redirects to http or https on ports 80 or 443 and does not validate the certificate of the HTTPS target, so even an expired certificate does not block renewal. What does break HTTP-01 is redirecting to a different host or port that does not serve the challenge file, or removing the port-80 listener. Keep the port-80 server in place, and let your ACME client renew as usual (automatic renewal). After the redirect works, add HSTS.
Troubleshooting
- ERR_TOO_MANY_REDIRECTS: the origin redirects requests that the CDN already received over HTTPS; see Step 4.
- Mixed content warnings after the switch: pages still load
http://scripts or images; see mixed content warning. - Certificate warning after the redirect: the HTTPS certificate does not cover the host you redirect to; see your connection is not private.
- Forms or API clients break: a 301 turned POST into GET; use 308 for API endpoints.
- Renewal fails after adding the redirect: the redirect points to another host or port; see certbot timeout during connect.
FAQ
Is a redirect enough to make the site HTTPS-only?
No. The first HTTP request is still sent in plain text and can be intercepted before the redirect. Add HSTS so browsers go straight to HTTPS on later visits, see enable HSTS.
Should the redirect be 301 or 302?
Use 301 (or 308) once everything works; search engines transfer ranking and browsers cache it. Use 302 or 307 while testing, because a cached 301 is hard to undo in users' browsers.
Do I need to exclude /.well-known/acme-challenge/ from the redirect?
Not for Let's Encrypt, which follows the redirect. Excluding it is still harmless and helps with other ACME CAs or tools that do not follow redirects.