skip to content

Apache

Apache httpd is the web server most legacy stacks still run, and the one you inherit when you take over an older platform. Interviewers ask about it to see whether you understand its configuration model, its process/thread architecture, and when you would keep it versus move to an event-driven server.

on this pageshow

explore

questions

18

In Apache mod_rewrite, what is the difference between `RewriteRule ^old/(.*)$ /new/$1 [L]` and the same rule written `[R=301,L]`?

level: juniorimportance: must knowfreq 72%

answer

  1. one request or two
  2. who learns about the change
  3. Location header or nothing
  4. L means last, not redirect
  5. scheme in the substitution forces it

basics

~20 s

With [L] alone the rewrite is internal: Apache serves the new path itself and the client never learns anything changed. With [R=301,L] Apache sends a redirect response carrying a Location header, so the browser issues a second request and its address bar updates.

solid answer

~50 s

`[L]` performs an *internal* rewrite. The URL is remapped inside the server, one request is served, the client sees the original URL in its address bar and has no idea the mapping happened. `[R]` makes it an *external* redirect: httpd answers with a 3xx status and a `Location` header pointing at the substitution, and the browser then makes a second, separate request for the new URL. `R` with no value defaults to 302; `R=301` asks for a permanent one. So the choice is really about who should know: use `[L]` for internal routing you never want exposed — a front controller, a pretty URL mapped onto a real file — and use `[R=…]` when the canonical address genuinely changed and you want clients, caches and search engines to adopt it. One trap: if the substitution starts with a scheme and host such as `https://example.com/x`, mod_rewrite forces an external redirect whether or not you wrote `R`.

code

apache · 10 lines
apache
RewriteEngine On

# Silent internal routing: address bar keeps showing /products/1234
RewriteRule ^products/([0-9]+)$ /product.php?id=$1 [L]

# Permanent move: client is redirected and updates its address bar
RewriteRule ^blog/(.*)$ /articles/$1 [R=301,L]

# Implicit redirect: an absolute target redirects even without R
RewriteRule ^shop/(.*)$ https://shop.example.com/$1 [L]

go deeper

for a junior

Be able to say plainly that [L] keeps everything inside the server while [R] sends the client a redirect it must follow, and that the address bar only changes in the second case.

for a middle

Explain that L only stops rule processing and carries no redirect meaning, that bare [R] is 302, and that a substitution starting with a scheme redirects implicitly.

for a senior

Show the diagnostic instinct: check the status line with curl before touching config, and account for the extra round trip an external redirect adds on every affected request.

for a principal

Own the canonicalisation policy — which URL forms are permanent redirects versus silent rewrites — so that redirect chains do not accumulate across teams and years.

## Two different things called "rewriting" mod_rewrite can do two operations that look nearly identical in the config file and are completely different on the wire. An **internal rewrite** changes which resource the server maps this request to. It happens entirely inside httpd. One request arrives, one response goes back, and the URL the client asked for is the URL the client still believes in. An **external redirect** ends the current request with a 3xx status and a `Location` header. The client then decides to make a second request to the new URL. Two requests, two round trips, and the client's address bar now shows the new address. ```apache RewriteEngine On # Internal: /old/report.html is served from /new/report.html, silently. RewriteRule ^old/(.*)$ /new/$1 [L] # External: the client is told to go and ask for /new/report.html itself. RewriteRule ^old/(.*)$ /new/$1 [R=301,L] ``` ## What each flag means `L` stands for "last": stop processing further rules in this ruleset for this pass. It says nothing at all about redirecting. On its own it simply means "this substitution is the answer, don't keep matching". `R` stands for "redirect", and it is what turns the substitution into a response rather than a remapping. Written bare, `[R]`, it uses 302. You may name the status explicitly — `R=301`, `R=302`, `R=303`, `R=307` — and mod_rewrite will also accept other 3xx values. What each of those status codes means to a browser or a cache is HTTP's business, not the rewrite engine's; what mod_rewrite guarantees is only that this is the status it will send with the `Location` header it builds. The two are almost always written together as `[R=301,L]`, because once you have decided to answer with a redirect, continuing to evaluate more rules against the same request is rarely what you want. ## The implicit redirect The substitution's shape can force the decision. If the substitution begins with a scheme and hostname — `http://`, `https://`, or `//host/path` — mod_rewrite cannot serve it internally, because the target is a different origin. It issues an external redirect even without `R`. This surprises people who write a rule pointing at their own site's absolute URL, expecting a silent internal remap, and instead find every request bouncing through a 302. The converse trap is a rule that *should* be a redirect but was written relative, so users keep seeing the old URL and nothing ever migrates. ## Choosing between them Use an internal rewrite when the external URL is the contract and the internal path is an implementation detail: - A front controller: every unmatched URL is served by `/index.php`, and the client never sees `index.php` in the address bar. - Clean URLs: `/products/1234` is really `/product.php?id=1234`, and you do not want the query-string form to leak or to become the address people bookmark. - Serving a different file per locale or per device from one URL. Use an external redirect when the address itself has changed and you want the world to update: - A moved or renamed section, where old links should end up at the new canonical URL. - Canonicalisation — one host, one trailing-slash form, one scheme — where having two live URLs for the same content is the problem you are solving. - Anything a search engine or a bookmark should learn about. ## Cost and visibility An internal rewrite is free in network terms: no extra round trip. An external redirect costs a full extra request, which on a mobile connection is tens to hundreds of milliseconds, and chains of them multiply that. That is a genuine argument for keeping internal routing internal. Visibility cuts the other way when you are debugging. An internal rewrite is invisible from outside: `curl -v` shows a single 200 for the original URL, so you cannot tell from the client whether a rule fired. `curl -sI https://example.com/old/x` immediately shows a redirect, status and `Location` and all. When an internal rule misbehaves you need the server side — raise `LogLevel` to a trace level (httpd 2.4 removed the old `RewriteLog` directive) and read the rewrite trace in the error log. One more consequence worth internalising: because an internal rewrite re-enters the request-mapping machinery, a per-directory rule whose output still matches its own pattern will loop. An external redirect loops too, but the browser stops it and shows a redirect-loop error; the internal version burns server-side recursion until httpd gives up and returns 500.

  • If you write [R] with no status code, what does mod_rewrite send?
    302. Bare `[R]` is equivalent to `[R=302]` — mod_rewrite's default is the temporary form, on the principle that a temporary redirect is the safer thing to get wrong. If you mean the move to be permanent you must say `R=301` (or `R=308`) explicitly. Getting this wrong is common and one-directional in its pain: a wrongly-permanent redirect is cached by clients long after you fix the config.
  • How can you tell from outside the server whether a URL was rewritten internally or redirected?
    Ask for it and look at the status line — `curl -sI https://example.com/old/x`. A redirect shows a 3xx status and a `Location` header, and `curl -L` will visibly follow it. An internal rewrite shows a single 200 for the URL you asked for, with nothing to distinguish it from a file that really lives there. To confirm an internal rule fired you need the server side: raise `LogLevel` to a trace level and read the rewrite trace.
  • Why might a rule you wrote without R still redirect the client?
    Because the substitution begins with a scheme and host — `https://…` or `//host/…`. mod_rewrite cannot map another origin to a local file, so it issues an external redirect regardless of flags. If you intended a silent remap within your own site, write the substitution as an absolute *path* (`/new/$1`) rather than an absolute URL.

saying these in an interview costs you the question

  • Says [L] updates the browser's address bar
  • Thinks bare [R] sends 301
  • Believes [L] means "redirect last"
  • Assumes an internal rewrite costs an extra round trip
  • Writes an absolute URL and expects a silent rewrite

context

open as a page

In Apache httpd, what work does the server do on every single request when .htaccess files are enabled, and how does the AllowOverride directive change it?

level: middleimportance: must knowfreq 66%

basics

~20 s

Apache httpd looks for an .htaccess file in every directory along the path to the requested file, on every request, and re-parses each one it finds. AllowOverride None removes that lookup entirely; any other value enables it.

open as a page

Apache httpd ships three main multi-processing modules — mpm_prefork, mpm_worker and mpm_event. How does each one map connections to processes and threads, and what specifically does mpm_event change?

level: middleimportance: must knowfreq 75%

basics

~20 s

mpm_prefork dedicates one single-threaded process per connection; mpm_worker runs many threads inside a few processes; mpm_event is worker plus a listener thread that holds idle keep-alive connections so no worker thread is parked on them.

open as a page

In Apache httpd running mpm_event, how do MaxRequestWorkers, ServerLimit and ThreadsPerChild relate, and what happens if you set MaxRequestWorkers higher than ServerLimit multiplied by ThreadsPerChild?

level: middleimportance: must knowfreq 60%

basics

~20 s

MaxRequestWorkers is the total worker threads across all children, and it cannot exceed ServerLimit times ThreadsPerChild. Set it higher and httpd logs a startup warning and silently lowers it to that product, so your intended concurrency never takes effect.

open as a page

In Apache httpd with mod_proxy, what does ProxyPassReverse do that ProxyPass does not, and what breaks in production if you configure ProxyPass alone?

level: middleimportance: must knowfreq 64%

basics

~20 s

ProxyPass maps an inbound URL path to a backend and forwards requests. ProxyPassReverse works on the way back, rewriting Location, Content-Location and URI response headers so a backend redirect pointing at the internal address is translated into the public URL.

open as a page

In Apache httpd 2.4, how does the server decide which <VirtualHost> block handles an incoming request, and what happens when the request's Host header matches no ServerName or ServerAlias?

level: middleimportance: must knowfreq 70%

basics

~20 s

Apache matches in two stages: first it narrows virtual hosts to those whose <VirtualHost> address and port fit the connection, then it compares the Host header against ServerName and ServerAlias. If no name matches, the first vhost listed for that address:port serves the request.

open as a page

On a Debian or Ubuntu Apache httpd install, what do the a2enmod and a2ensite commands actually do, and why can editing a file under sites-available leave the running server unchanged?

level: juniorimportance: should knowfreq 54%

basics

~20 s

They create symlinks: a2enmod links a module's files from mods-available into mods-enabled, a2ensite links a vhost file from sites-available into sites-enabled. Apache only reads the enabled directories, and only at startup or reload — so an un-linked or un-reloaded file changes nothing.

open as a page

An Apache rewrite rule that worked in the main server configuration stops matching after you move it into an .htaccess file: `RewriteRule ^/products/(.*)$ /catalog/$1 [L]` now never fires. Why, and what is RewriteBase for?

level: middleimportance: should knowfreq 54%

basics

~20 s

In an .htaccess file, mod_rewrite strips the directory's own path prefix and the leading slash before matching, so a pattern beginning with ^/ can never match. RewriteBase declares the URL path that directory corresponds to, so relative substitutions expand correctly.

open as a page

Why does serving PHP through the in-process Apache module mod_php effectively pin the server to mpm_prefork, and what changes when the same application moves to php-fpm?

level: middleimportance: should knowfreq 50%

basics

~20 s

mod_php runs the interpreter inside each httpd child, and the interpreter build plus its extensions are generally not thread-safe, so only single-threaded prefork children are safe. php-fpm moves PHP into its own process pool, freeing httpd to run mpm_event.

open as a page

An Apache site whose .htaccess routes everything to a front controller starts returning 500s, and the error log reports that the request exceeded the limit of 10 internal redirects. What causes that loop, and how do you write the rule so it cannot happen?

level: seniorimportance: should knowfreq 46%

basics

~20 s

A per-directory rewrite is re-injected into the request-mapping cycle, so the .htaccess runs again against its own output. If the rule matches the rewritten URL too, it loops until httpd hits LimitInternalRecursion and returns 500. Guard the rule so it cannot match its result.

open as a page

An Apache httpd server stops keeping up: requests queue for seconds and the error log repeats 'server reached MaxRequestWorkers setting, consider raising the MaxRequestWorkers setting', while the machine's CPU is mostly idle. How do you work out whether raising it is actually the right fix?

level: seniorimportance: should knowfreq 52%

basics

~20 s

Idle CPU with every worker busy means workers are blocked waiting, not computing. Find what they wait on — a slow backend, or idle keep-alive connections under prefork or worker — before raising the ceiling, because raising it multiplies memory use and downstream load.

open as a page

In Apache httpd 2.4, what is the difference between the <Directory>, <Location> and <Files> containers, and in what order are they merged when several apply to the same request?

level: seniorimportance: should knowfreq 46%

basics

~20 s

<Directory> and <Files> scope rules by filesystem path and filename; <Location> scopes by URL. Apache merges them in a fixed order — Directory, then DirectoryMatch, then Files, then Location, then <If> — with later sections overriding earlier ones.

open as a page

You own an Apache fleet where every application ships its own .htaccess file. How would you decide whether to keep AllowOverride enabled or move those rules into the server configuration?

level: principalimportance: should knowfreq 33%

basics

~20 s

Decide on who needs to change rules without a server reload. Where teams control the server config, hoist rules into <Directory> blocks and set AllowOverride None: config is parsed once, reviewable and syntax-checked. Keep .htaccess only where delegation to untrusted or reload-less tenants is the actual requirement.

open as a page

Your team runs Apache httpd with mpm_event in front of an application, and someone proposes replacing it with an event-driven server such as nginx 'for performance'. How would you decide, and where does a process-and-thread server genuinely lose to an event loop?

level: principalimportance: should knowfreq 45%

basics

~20 s

Decide from the traffic's shape, not the reputation. A thread-per-request server loses when concurrent connections vastly outnumber active requests — each needs a thread and its stack, versus a few KB of state in an event loop. If mpm_event already keeps up, the bottleneck is usually the application.

open as a page

You inherit an Apache httpd server whose single httpd.conf defines 120 virtual hosts, and every change requires a full restart. How would you restructure that configuration and the process for changing it?

level: principalimportance: should knowfreq 34%

basics

~20 s

Split it into one file per virtual host pulled in by Include, pin the catch-all vhost first through the file naming, generate the repetitive parts from a template, validate with apachectl configtest and an apachectl -S diff in CI, and apply changes with a graceful reload instead of a restart.

open as a page

How do you determine which multi-processing module a running Apache httpd 2.4 instance is using, and how would you switch it to a different one?

level: juniorimportance: nice to knowfreq 38%

basics

~20 s

Run httpd -V (apache2ctl -V on Debian) and read the Server MPM line, or list loaded modules with httpd -M. Switching means loading a different mpm_* module — only one may be active — and doing a full restart.

open as a page

In Apache mod_rewrite, what happens to a request's original query string when a RewriteRule substitution contains a query string of its own, and what does the QSA flag change?

level: middleimportance: nice to knowfreq 42%

basics

~20 s

By default the original query string survives only if the substitution has none of its own; a substitution containing a question mark replaces it. The QSA flag appends the original to the new one instead, and QSD discards it deliberately.

open as a page

How would you expose Apache httpd's mod_status page safely, and why can an IP-based restriction on /server-status fail to protect it when Apache sits behind a reverse proxy or CDN?

level: seniorimportance: nice to knowfreq 30%

basics

~20 s

Scope it with a <Location> block using SetHandler server-status and an explicit Require rule. Behind a proxy, the connection's peer is the proxy, so Require ip matches for every visitor and the restriction fails open unless mod_remoteip restores the real client address.

open as a page