Skip to content
DevOps devops webservers 5 min read

.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)

SituationUse .htaccess?Better option
Shared hosting, no access to main configYesNone — it is your only choice
You cannot restart ApacheYesNone
A CMS like WordPress ships its own rulesYesKeep their file
You own the server and control the main configNoPut rules in the virtual host (a per-site config block)
High-traffic site where speed mattersNoMain 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 .htaccess file.

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:

FlagMeaning
R=301Send 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 None everywhere by default and only enable All for 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 .htaccess for shared hosting or app-supplied files.
  • Always use R=301 for permanent redirects so search engines pass on link value; use R=302 only for genuinely temporary moves.
  • Keep a RewriteCond %{REQUEST_FILENAME} !-f guard before front-controller rules so real static files are never rewritten.
  • Run sudo apache2ctl configtest after config changes and curl -I to verify redirects before going live.
  • Never store secrets in .htaccess; protect sensitive files with Require all denied blocks instead.
  • Back up the file before editing — a single bad regex can return a 500 Internal Server Error for the whole folder.
Last updated June 15, 2026
Was this helpful?