Skip to content

.htaccess force HTTPS: redirect to https, enable HSTS and add security headers in Apache

Published 7 October 20268 min readBy the SmoothSeen editorial team

The cleanest way to force HTTPS in Apache is a Redirect permanent in the port 80 VirtualHost. If all you have is .htaccess, use mod_rewrite with one 301 rule that sends visitors to https and to your canonical hostname in a single hop. Then add HSTS with Header always set on HTTPS responses only, starting with a short max-age, plus the other security headers.

Key points

  • The Apache documentation advises against .htaccess when you can edit the main configuration; anything in it is read on every request.
  • With mod_rewrite you can send http and the bare domain to https://www in a single 301 redirect; we checked it on Apache httpd 2.4.69.
  • HSTS only counts when it arrives over HTTPS. Start with max-age=300 and raise it in stages; preload is hard to undo and forces HTTPS on every subdomain.
  • Header always set makes the headers appear on redirects and 404 errors too, not just on 200 responses.
  • IfModule hides mistakes. Without it, a missing module gives a 500 error with the cause in the log; with it, the headers silently disappear.

To check it on your own site: SEO audit

On this page

Apache controls three things covered here: where each request is redirected, which headers travel with each response and, if you have server access, which certificate and TLS versions it uses. The certificate itself is not configured in .htaccess: your host installs it, or you set it in the VirtualHost. Every snippet in this guide was tested on 7 October 2026 with Apache httpd 2.4.69 in its official Docker image (httpd:2.4), using a self-signed certificate and curl from a second container. Compression, caching and AI bots are in the companion guide on .htaccess compression, caching and bots.

VirtualHost or .htaccess?

An .htaccess file is a configuration file that Apache looks for in each directory, on every request. The Apache documentation is blunt: if you can edit the main configuration file, put everything there, mod_rewrite rules included, because it is loaded once at start-up rather than on every request1. In .htaccess, regular expressions are also recompiled on every request1.

.htaccess exists for people without root access, such as shared hosting customers. And it only works if the administrator allows it with AllowOverride, whose default is None: with that value Apache ignores .htaccess files completely1.

Situation
Your own server or VPS
Where to configure it
VirtualHost
Why
Read once at start-up and checked by httpd -t
Situation
Shared hosting without config access
Where to configure it
.htaccess
Why
It is the only thing you can edit
Situation
Host already redirects to https from its control panel
Where to configure it
Only the headers in .htaccess
Why
Two chained redirects add extra hops

Step 1: redirect http to https in the VirtualHost

The Apache documentation says the cleanest way to send http to https is a Redirect in a VirtualHost dedicated to port 80, with no mod_rewrite at all2. If you also want example.com to go to www.example.com, add a port 443 VirtualHost for the bare domain:

<VirtualHost *:80>
    ServerName www.example.com
    ServerAlias example.com
    Redirect permanent "/" "https://www.example.com/"
</VirtualHost>

<VirtualHost *:443>
    ServerName example.com
    SSLEngine on
    SSLCertificateFile "/path/to/certificate.crt"
    SSLCertificateKeyFile "/path/to/private.key"
    Header always set Strict-Transport-Security "max-age=300"
    Redirect permanent "/" "https://www.example.com/"
</VirtualHost>

<VirtualHost *:443>
    ServerName www.example.com
    DocumentRoot "/var/www/example"
    SSLEngine on
    SSLCertificateFile "/path/to/certificate.crt"
    SSLCertificateKeyFile "/path/to/private.key"
    Header always set Strict-Transport-Security "max-age=300"
    Header always set X-Content-Type-Options "nosniff"
    Header always set Referrer-Policy "strict-origin-when-cross-origin"
    Header always set X-Frame-Options "SAMEORIGIN"
    Header always set Permissions-Policy "camera=(), microphone=(), geolocation=()"
    Header always set Content-Security-Policy-Report-Only "default-src 'self'; frame-ancestors 'self'; report-uri /csp-reports"
</VirtualHost>

One version of each URL avoids http and https duplicates, and Google's page experience guide asks whether your pages are served securely3. In our test, http://example.com/blog/?a=1 reached https://www.example.com/blog/?a=1 with a single 301: Redirect keeps both the path and the query string. After every change, httpd -t (or apachectl configtest) checks the syntax before you reload.

Step 1b: in .htaccess, https and www in one hop

If .htaccess is all you have, the Apache documentation suggests mod_rewrite with the %{HTTPS} variable, which is on when the connection is encrypted2. This version merges the https redirect and the canonical hostname redirect, so nobody goes through two 301s in a row:

RewriteEngine On
RewriteCond %{HTTPS} !=on [OR]
RewriteCond %{HTTP_HOST} !^www\.example\.com$ [NC]
RewriteRule ^ https://www.example.com%{REQUEST_URI} [R=301,L,NE]

Line by line:

  1. The rule fires if the connection is not HTTPS or the hostname is not exactly www.example.com.
  2. R=301 is a permanent redirect; L stops processing further rules; NE stops Apache from re-encoding characters that were already encoded in the URL.
  3. The query string (?a=1) is kept automatically, because the target URL contains no ?.

Result on Apache 2.4.69: all four combinations (http or https, with or without www) end at https://www.example.com/ in one hop. If you prefer the bare domain, change both occurrences of the hostname.

A warning before you paste it: if your site sits behind a CDN or proxy that terminates TLS, Apache receives the request over http and %{HTTPS} is never on. The rule would always fire and loop. The Apache documentation suggests checking the X-Forwarded-Proto header set by the proxy instead, and warns you to trust it only if you control that proxy and it overwrites the header on every request, because anyone can forge it2. Redirecting to https at the CDN is usually simpler.

Step 2: HSTS without locking yourself in

HSTS (Strict-Transport-Security) is a header that tells the browser to use HTTPS for that hostname for max-age seconds, never plain http. Browsers ignore it when it arrives over an unencrypted connection4, so in an .htaccess that serves both ports it is best sent over HTTPS only:

Header always set Strict-Transport-Security "max-age=300" "expr=%{HTTPS} == 'on'"

The expr= condition is how mod_headers documents setting a header only when an expression is true5. In our test the http response had no HSTS header and the https one did.

Raise max-age in stages, as the HSTS preload service recommends: five minutes (300), one week (604800), one month (2592000), waiting out the full max-age of each stage before moving on6. Then a year (31536000).

Two directives deserve care:

  • includeSubDomains applies the policy to every subdomain4. If intranet.example.com or your webmail has no HTTPS, it will stop opening.
  • preload is for getting onto the list browsers ship with. It requires a max-age of at least a year, includeSubDomains, HTTPS on every subdomain and the header on redirects too; getting off the list takes months, with no guarantees for some browsers6. MDN notes that preload is not part of the specification4.

Step 3: the other security headers

Header
X-Content-Type-Options
Example value
nosniff
What it does
Stops the browser guessing a file type other than the declared one
Header
Referrer-Policy
Example value
strict-origin-when-cross-origin
What it does
Only the origin, not the path, reaches other sites. It is the browser default; declaring it makes it explicit7
Header
X-Frame-Options
Example value
SAMEORIGIN
What it does
Stops other sites framing yours. CSP frame-ancestors supersedes it8
Header
Permissions-Policy
Example value
camera=(), microphone=(), geolocation=()
What it does
Turns off features your site does not use. MDN lists it as limited availability9
Header
Content-Security-Policy-Report-Only
Example value
default-src 'self'; ...
What it does
Trials a CSP without blocking anything: it only reports

The complete .htaccess block, exactly as tested:

Header always set Strict-Transport-Security "max-age=300" "expr=%{HTTPS} == 'on'"
Header always set X-Content-Type-Options "nosniff"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=()"
Header always set Content-Security-Policy-Report-Only "default-src 'self'; frame-ancestors 'self'; report-uri /csp-reports"

Why always: mod_headers keeps two header tables, and only the always one is used on non-2xx responses such as redirects and errors5. In our test the 404 carried all six headers, and the 301 from bare https to www https carried HSTS.

A Report-Only CSP blocks nothing: the browser records what it would have blocked. To receive those reports on your server you need report-to or the older report-uri10. Once a few weeks pass without surprises, rename the header to Content-Security-Policy. And do not add X-XSS-Protection: 1: MDN warns it can create vulnerabilities in otherwise safe sites and recommends CSP instead11.

IfModule: yes or no?

Many sample .htaccess files wrap everything in <IfModule mod_headers.c>. The Apache documentation says that section is only needed when one file must work on installations with and without the module, and that in normal operation it is unnecessary12. We tested it with mod_headers not loaded:

Test
Header without <IfModule>
Result
500 error, and the log says "Invalid command 'Header', perhaps misspelled or defined by a module not included"
Test
Header inside <IfModule mod_headers.c>
Result
200, no headers at all and no warning
Test
Typo (Header alwys set) in .htaccess
Result
500 error, even though httpd -t reports "Syntax OK"

The practical takeaway: in your own .htaccess, leave <IfModule> out so a failure is visible. And remember that httpd -t does not read .htaccess files: after each change, request a page.

How to check it

  1. Redirect. curl -I http://example.com/ should return 301 and a Location header with the final https URL. curl -IL shows every hop: one is right.
  2. Headers. curl -I https://www.example.com/ should show all six. Try a URL that does not exist too: the 404 should carry them.
  3. HSTS over HTTPS only. The http:// response should not include Strict-Transport-Security.
  4. Public grade. Mozilla's HTTP Observatory analyses the redirect, HSTS and the other headers and gives a grade13.

What SmoothSeen does with this

Within its SEO analysis, SmoothSeen checks whether the page answers over HTTPS, whether the http version redirects to https and which security headers it sends, using Mozilla's HTTP Observatory. It does not log into your server or read your .htaccess: it sees what a browser sees and tells you what is missing, alongside the rest of the analysis in a prioritised action plan.

What to do this week

Run curl -IL against the http bare domain and count the hops to the final URL. If there is more than one, or if the 404 is missing headers, apply the matching step and start HSTS at max-age=300. To see all of this next to the rest of your technical SEO, run an SEO audit.

Frequently asked questions

Why does my .htaccess file do nothing?

Most likely the server has AllowOverride None for that directory, which is Apache's default: the file is then ignored entirely. Also check that it is named exactly .htaccess, with the leading dot, and that it sits in the public web root. If your host does not let you change AllowOverride, ask their support team.

Should I use a 301 or a 302 redirect for HTTPS?

A 301, because the move is permanent. The Apache documentation explains that a permanent redirect tells search engines to update their index. A 302 says the move is temporary, so the old URL may keep showing up. Keep 302 for short tests and switch it as soon as you have confirmed everything works.

How do I turn HSTS off if I get it wrong?

Send the same header with max-age=0 over HTTPS: the browser forgets the policy as soon as it receives it. Over http it does nothing, because browsers ignore HSTS on unencrypted connections. If you also requested preloading, you will have to ask to be removed from the list, and reaching every user takes months, which is why starting with short values matters.

Do these headers improve Google rankings?

Not directly. Google lists "are your pages served in a secure fashion?" among its page experience questions, but says that, beyond Core Web Vitals, those aspects do not by themselves help a site rank higher. What you do gain is a single version of each URL, with no http and https duplicates, and better-protected visitors.

Sources

  1. 1Apache HTTP Server Tutorial: .htaccess files, Apache Software Foundation, accessed 7 October 2026.
  2. 2Redirecting and Remapping with mod_rewrite, Apache Software Foundation, accessed 7 October 2026.
  3. 3Understanding page experience in Google Search results, Google Search Central, updated 22 September 2026.
  4. 4Strict-Transport-Security, MDN Web Docs, updated 11 September 2026.
  5. 5Apache Module mod_headers, Apache Software Foundation, accessed 7 October 2026.
  6. 6HSTS Preload List Submission, Chromium, accessed 7 October 2026.
  7. 7Referrer-Policy, MDN Web Docs, updated 4 September 2026.
  8. 8X-Frame-Options, MDN Web Docs, updated 17 September 2026.
  9. 9Permissions-Policy, MDN Web Docs, updated 14 September 2026.
  10. 10Content-Security-Policy-Report-Only, MDN Web Docs, updated 22 March 2026.
  11. 11X-XSS-Protection, MDN Web Docs, updated 21 August 2026.
  12. 12Apache Core Features: IfModule and AllowOverride, Apache Software Foundation, accessed 7 October 2026.
  13. 13HTTP Observatory, Mozilla, accessed 7 October 2026.

How to cite this article

SmoothSeen. (2026, October 7). .htaccess force HTTPS: redirect to https, enable HSTS and add security headers in Apache. https://smoothseen.com/en/blog/apache-htaccess-https-hsts-security-headers/

Who writes this

SmoothSeen is a website audit tool that measures visibility in search engines and AI assistants and delivers reports under the agency's own brand.

This blog belongs to SmoothSeen: when an article discusses the product, it does so knowing the product is ours. Third-party figures link to their original source.

Change history

  • First version.

Keep reading

.htaccess force HTTPS, HSTS and security headers (tested)