Skip to content
DevOps devops domains 6 min read

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

CodeNameMeaningBrowser/search behaviorUse it when
301Moved PermanentlyThe resource has permanently moved to the new URLCached aggressively; search engines pass ranking to the HTTPS URLForcing HTTPS — this is what you want
302Found (temporary)The move is temporary; keep using the old URLNot cached long-term; ranking stays on the old URLA 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:

  • $host is the domain the visitor asked for (so example.com and www.example.com are each preserved).
  • $request_uri is 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 includeSubDomains flag forces all subdomains to HTTPS too, and browsers remember the rule for the full max-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 short max-age (like 300) 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 $host and $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 -t or apache2ctl configtest before reloading, and verify with curl -I.
Last updated June 15, 2026
Was this helpful?