Redirecting HTTP to HTTPS
Once you have an SSL/TLS certificate installed (SSL/TLS is the technology that encrypts traffic between a browser and your server), your site can be reached over https://. But the old, unencrypted http:// address still works too — and that is a problem. Visitors who type your domain without https, click an old link, or load a bookmark will land on the insecure version, sending their data in plain text. The fix is a redirect: a small rule on your web server that catches every HTTP request and tells the browser to come back over HTTPS instead. This page shows the exact configuration for both Nginx and Apache, and explains the details that matter.
What a redirect actually does
A web server (a program like Nginx or Apache that answers requests from browsers) listens on two TCP ports: port 80 for plain HTTP and port 443 for encrypted HTTPS. A redirect lives in the port-80 configuration. When a browser connects on port 80, the server does not serve the page — instead it replies with a status code and a Location header pointing at the https:// URL. The browser then automatically makes a second request to that secure address.
The status code you choose tells the browser how permanent the redirect is.
301 vs 302 — which status code to use
| Code | Name | Meaning | Browser/search behavior | Use it when |
|---|---|---|---|---|
| 301 | Moved Permanently | The resource has permanently moved to the new URL | Cached aggressively; search engines pass ranking to the HTTPS URL | Forcing HTTPS — this is what you want |
| 302 | Found (temporary) | The move is temporary; keep using the old URL | Not cached long-term; ranking stays on the old URL | A short-lived, temporary redirect only |
For an HTTP-to-HTTPS redirect you almost always want 301. HTTPS is not a temporary state — your site lives there permanently. A 301 also lets browsers remember the redirect and skip the extra round-trip on future visits, and it tells search engines to treat the HTTPS URL as the real one.
Be careful while testing. Browsers cache 301 redirects very aggressively, sometimes for a long time. If you set up a 301 with a mistake in it, your browser may keep following the broken redirect even after you fix the server. While experimenting, use a 302 first, or test in a private/incognito window so a bad cache does not confuse you.
Nginx: the redirect block
In Nginx, server configuration files for sites live in /etc/nginx/sites-available/ (on Ubuntu 22.04 and 24.04 LTS), and the ones that are switched on are symlinked into /etc/nginx/sites-enabled/. Open your site’s file:
sudo nano /etc/nginx/sites-available/example.com
The clean pattern is two separate server blocks: one on port 80 whose only job is to redirect, and one on port 443 that actually serves your site.
# Port 80 — plain HTTP. Its only job is to redirect to HTTPS.
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
# Port 443 — encrypted HTTPS. This serves the real site.
server {
listen 443 ssl;
listen [::]:443 ssl;
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;
index index.html;
}
The key line is return 301 https://$host$request_uri;. Here:
$hostis the domain the visitor asked for (soexample.comandwww.example.comare each preserved).$request_uriis the full path and query string they requested (for example/blog/post?id=5).
Together they rebuild the exact same URL on https://, so a visitor going to http://example.com/contact lands on https://example.com/contact, not just the homepage. The return directive is the modern, efficient way to do this — avoid the older rewrite approach, which is slower and easier to get wrong.
Test your configuration for syntax errors, then reload Nginx so it picks up the change without dropping connections:
sudo nginx -t
sudo systemctl reload nginx
Output:
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
Verify the redirect actually fires using curl (a command-line tool for making HTTP requests). The -I flag asks for headers only:
curl -I http://example.com
Output:
HTTP/1.1 301 Moved Permanently
Server: nginx
Location: https://example.com/
The 301 and the Location: https://... header confirm it works.
Apache: the redirect virtual host
Apache (another popular web server) stores its site configs in /etc/apache2/sites-available/ on Ubuntu, and you enable them with a2ensite. The same two-block idea applies, but Apache calls them <VirtualHost> blocks. Edit the file:
sudo nano /etc/apache2/sites-available/example.com.conf
# Port 80 — redirect everything to HTTPS.
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
Redirect permanent / https://example.com/
</VirtualHost>
# Port 443 — the real, encrypted site.
<VirtualHost *:443>
ServerName example.com
ServerAlias www.example.com
DocumentRoot /var/www/example.com
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
</VirtualHost>
Redirect permanent sends a 301 (the word permanent is the 301; use Redirect temp for a 302). Make sure the rewrite and ssl modules are on, test the config, and reload:
sudo a2enmod ssl rewrite
sudo apache2ctl configtest
sudo systemctl reload apache2
Output:
Syntax OK
HSTS — telling browsers to never use HTTP again
A redirect still requires one insecure HTTP request before the browser is bounced to HTTPS. HSTS (HTTP Strict Transport Security) closes that gap. It is a response header that tells the browser: “for the next N seconds, never even try HTTP for this domain — go straight to HTTPS.” After the first visit, the browser stops sending HTTP requests entirely.
Add the header inside your HTTPS (port 443) block only — never on port 80.
Nginx:
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
Apache (needs the headers module: sudo a2enmod headers):
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
max-age=31536000 is one year in seconds.
Turn on HSTS only once you are confident HTTPS works on every subdomain. The
includeSubDomainsflag forces all subdomains to HTTPS too, and browsers remember the rule for the fullmax-age. If a subdomain still needs HTTP, browsers will refuse to load it — and you cannot easily undo it on a visitor’s machine. Start with a shortmax-age(like300) while testing.
Let Certbot do it for you
Certbot is the official tool for getting free Let’s Encrypt certificates. When you run it, it offers to write the HTTP-to-HTTPS redirect for you automatically:
sudo certbot --nginx -d example.com -d www.example.com
During the run it asks whether to redirect HTTP traffic to HTTPS. Choose 2: Redirect, and Certbot edits your server config to add the exact 301 redirect shown above — no manual editing needed. The same works with --apache.
Best practices
- Always use 301 (permanent) for HTTP-to-HTTPS, not 302.
- Keep HTTP and HTTPS in separate server/virtual-host blocks — the port-80 block should only redirect, nothing else.
- Use
$hostand$request_uri(Nginx) so the full original URL is preserved, not just the homepage. - Add HSTS for defense in depth, but only after HTTPS is fully working on every subdomain.
- Let Certbot generate the redirect for you to avoid typos in hand-written config.
- Always run
nginx -torapache2ctl configtestbefore reloading, and verify withcurl -I.