.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
- VirtualHost or .htaccess?
- Step 1: redirect http to https in the VirtualHost
- Step 1b: in .htaccess, https and www in one hop
- Step 2: HSTS without locking yourself in
- Step 3: the other security headers
- IfModule: yes or no?
- How to check it
- What SmoothSeen does with this
- What to do this week
- Frequently asked questions
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:
- The rule fires if the connection is not HTTPS or the hostname is not exactly
www.example.com. R=301is a permanent redirect;Lstops processing further rules;NEstops Apache from re-encoding characters that were already encoded in the URL.- 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:
includeSubDomainsapplies the policy to every subdomain4. Ifintranet.example.comor your webmail has no HTTPS, it will stop opening.preloadis for getting onto the list browsers ship with. It requires amax-ageof 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 thatpreloadis 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-ancestorssupersedes 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
Headerwithout<IfModule>- Result
- 500 error, and the log says "Invalid command 'Header', perhaps misspelled or defined by a module not included"
- Test
Headerinside<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 -treports "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
- Redirect.
curl -I http://example.com/should return301and aLocationheader with the final https URL.curl -ILshows every hop: one is right. - Headers.
curl -I https://www.example.com/should show all six. Try a URL that does not exist too: the 404 should carry them. - HSTS over HTTPS only. The
http://response should not includeStrict-Transport-Security. - 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
- 1Apache HTTP Server Tutorial: .htaccess files, Apache Software Foundation, accessed 7 October 2026.
- 2Redirecting and Remapping with mod_rewrite, Apache Software Foundation, accessed 7 October 2026.
- 3Understanding page experience in Google Search results, Google Search Central, updated 22 September 2026.
- 4Strict-Transport-Security, MDN Web Docs, updated 11 September 2026.
- 5Apache Module mod_headers, Apache Software Foundation, accessed 7 October 2026.
- 6HSTS Preload List Submission, Chromium, accessed 7 October 2026.
- 7Referrer-Policy, MDN Web Docs, updated 4 September 2026.
- 8X-Frame-Options, MDN Web Docs, updated 17 September 2026.
- 9Permissions-Policy, MDN Web Docs, updated 14 September 2026.
- 10Content-Security-Policy-Report-Only, MDN Web Docs, updated 22 March 2026.
- 11X-XSS-Protection, MDN Web Docs, updated 21 August 2026.
- 12Apache Core Features: IfModule and AllowOverride, Apache Software Foundation, accessed 7 October 2026.
- 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/
Keep reading
What is SEO? How search engine optimisation works and how to improve it in 2026
What SEO is, how Google decides which pages to show and a prioritised checklist to improve your rankings with free tools.
.htaccess gzip and Brotli: browser caching and blocking AI bots in Apache
How to enable gzip and Brotli, set browser caching and block AI training bots in Apache .htaccess without dropping out of ChatGPT search. Tested.
Broken links: what they are, how to find them and how to fix them
What a broken link is, what Google actually says about 404 errors, and how to find and fix broken links with Search Console, a crawler and curl.