.htaccess & URL Rewriting
An .htaccess file (the name literally means “hypertext access”) is a small configuration file you drop into a folder of your website to change how the Apache web server behaves for that folder and everything inside it. The big idea is that you can tweak server settings without touching Apache’s main config file and without restarting Apache. The most popular use is “URL rewriting” with a module called mod_rewrite — turning ugly URLs like index.php?id=5 into clean ones like /products/5, and forcing every visitor onto HTTPS (the secure, encrypted version of HTTP). This page explains how .htaccess works on Ubuntu, when you should use it, and when you should not.
What is .htaccess and how it works
When Apache (a popular open-source web server) receives a request, it walks down the directory path that maps to the URL. In every directory along the way, it looks for a file named exactly .htaccess. If it finds one, it reads it and applies those rules before serving the page. Because the file is read on every single request, changes take effect instantly — no systemctl reload needed.
The leading dot in the filename makes it a hidden file on Linux. Use ls -a to see it:
ls -la /var/www/html
Output:
drwxr-xr-x 2 www-data www-data 4096 Jun 15 10:02 .
drwxr-xr-x 4 root root 4096 Jun 15 09:58 ..
-rw-r--r-- 1 www-data www-data 412 Jun 15 10:02 .htaccess
-rw-r--r-- 1 www-data www-data 1024 Jun 15 10:00 index.php
When to use it (and when not)
| Situation | Use .htaccess? | Better option |
|---|---|---|
| Shared hosting, no access to main config | Yes | None — it is your only choice |
| You cannot restart Apache | Yes | None |
| A CMS like WordPress ships its own rules | Yes | Keep their file |
| You own the server and control the main config | No | Put rules in the virtual host (a per-site config block) |
| High-traffic site where speed matters | No | Main config (see performance note below) |
If you have full root access to the server, you almost always get faster, more centralised, and more secure results by putting your rules directly in the site’s virtual host file under
/etc/apache2/sites-available/instead of an.htaccessfile.
AllowOverride — the master switch
By default on Ubuntu, Apache ignores .htaccess files for security and speed. You must explicitly turn them on with the AllowOverride directive inside your site config. AllowOverride All lets .htaccess override everything; AllowOverride None disables it completely.
Edit your virtual host:
sudo nano /etc/apache2/sites-available/000-default.conf
Add a <Directory> block that points at your web root:
<VirtualHost *:80>
DocumentRoot /var/www/html
<Directory /var/www/html>
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
AllowOverride All is the line that makes .htaccess work. Then reload Apache so the new directory rule takes effect:
sudo systemctl reload apache2
Enabling mod_rewrite
mod_rewrite is the Apache module (a plug-in feature) that does URL rewriting. It is installed with Apache but disabled by default. Enable it and restart:
sudo a2enmod rewrite
sudo systemctl restart apache2
Output:
Enabling module rewrite.
To activate the new configuration, you need to run:
systemctl restart apache2
You can confirm it is loaded:
apache2ctl -M | grep rewrite
Output:
rewrite_module (shared)
Writing rewrite rules
A rewrite rule has three parts: the directive RewriteRule, a pattern (a regular expression — a mini-language for matching text), and a target (where the request should go). You almost always start a block with RewriteEngine On to switch rewriting on.
Force HTTPS
This is the single most common rule. It redirects any plain http:// request to the secure https:// version. Create or edit the .htaccess in your web root:
sudo nano /var/www/html/.htaccess
RewriteEngine On
# 1. Force HTTPS for every request
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
Here RewriteCond is a condition: “only run the next rule when HTTPS is off.” %{HTTP_HOST} is the domain and %{REQUEST_URI} is the path the user asked for. The flags in brackets matter:
| Flag | Meaning |
|---|---|
R=301 | Send a 301 “permanent redirect” so browsers and search engines update the URL |
L | ”Last” — stop processing more rules if this one matches |
NC | ”No case” — match upper and lower case alike |
QSA | ”Query string append” — keep any ?key=value part of the URL |
Route everything to index.php (a front controller)
Most PHP frameworks (Laravel, Symfony) and CMSs send every URL to a single index.php that decides what to do. The rule below sends a request to index.php only if the requested path is not a real file or folder:
# 2. Pretty URLs: route to index.php unless a real file/dir exists
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?path=$1 [QSA,L]
!-f means “not an existing file” and !-d means “not an existing directory.” So real assets like style.css are served normally, while /products/5 is handed to index.php?path=products/5.
Test it
curl -I http://localhost/products/5
Output:
HTTP/1.1 301 Moved Permanently
Location: https://localhost/products/5
Server: Apache/2.4.58 (Ubuntu)
The 301 and Location header confirm the HTTPS redirect fired.
The performance cost
Because Apache must search every directory in the request path for an .htaccess file on every request, it adds disk lookups and parsing work to each page load. The main config file, by contrast, is read once when Apache starts and cached in memory. On a busy site this overhead is real and measurable.
# Slow: .htaccess read on every request, every directory
# Fast: same rules placed inside <Directory> in the vhost, parsed once at startup
So treat .htaccess as a convenience for environments where you lack main-config access, not as the default home for your rules.
Best Practices
- Set
AllowOverride Noneeverywhere by default and only enableAllfor the specific directory that needs it — fewer directory scans, smaller attack surface. - Prefer the virtual host file for rules when you control the server; reserve
.htaccessfor shared hosting or app-supplied files. - Always use
R=301for permanent redirects so search engines pass on link value; useR=302only for genuinely temporary moves. - Keep a
RewriteCond %{REQUEST_FILENAME} !-fguard before front-controller rules so real static files are never rewritten. - Run
sudo apache2ctl configtestafter config changes andcurl -Ito verify redirects before going live. - Never store secrets in
.htaccess; protect sensitive files withRequire all deniedblocks instead. - Back up the file before editing — a single bad regex can return a
500 Internal Server Errorfor the whole folder.