What this post covers
This is the seventh post in the "Digging into Apache httpd's Source" series. Part 6 looked at the module struct that represents one module, and at how a module plugs into various moments of the server through hooks. Besides the function that registers hooks, that struct had five more slots, all about configuration: two functions that create config structs, two that merge them, and the directive table (include/http_config.h lines 384–412). This post is about those five slots.
There are three questions to answer.
- In what order is a line you write in
httpd.confread, and how does it reach a module's C function? - Where do errors like
... not allowed herecome from, and what is compared with what? Where doesAllowOverridefit in? - Where are the values you write inside
<Directory>,<Location>and<VirtualHost>kept, and when they overlap, which one wins? When does that "combining" actually happen?
| Item | Details |
|---|---|
| Project | Apache httpd |
| Version | 2.5.1-dev (trunk) |
| Commit | e4af30fe6d (2026-10-24) |
| Files read most | server/config.c, server/util_cfgtree.c, include/http_config.h, server/core.c, server/request.c (parts) |
Every line number in this post refers to that commit. Part 1 explains how to get the source at the same commit and build the example container.
server/config.c alone is 2,564 lines, and server/core.c, which handles the sections, is 6,225 lines. This post keeps only the main thread that someone opening the httpd source for the first time can follow. What was left out is named, with its location, under "What this post leaves out" at the end.
Words to know first
A directive is the first word on a line of the config file. In DocumentRoot /var/www, DocumentRoot is the directive and /var/www is its argument. The httpd core itself owns very few directives; most of them are registered by modules saying "I handle a directive with this name".
A section (the source also calls it a container) is a block opened and closed with angle brackets, like <Directory /var/www> … </Directory>. The directives inside apply only to that block.
Server config and directory config. A module defines its own struct to hold its settings. These structs come in two kinds: a server config (per-server config), one for each server — the main server and every <VirtualHost> — and a directory config (per-directory config), one for each block such as <Directory>, <Location> or .htaccess. In the module struct, create_server_config and create_dir_config are the functions that create these structs, and merge_server_config and merge_dir_config are the ones that combine two of them (include/http_config.h lines 384–408). All four are function pointers — slots that hold "the address of a function to call later". C function pointers were explained in Part 2.
A config vector is a void * array with one slot per module. The httpd core does not know what any module's config struct looks like, so it holds them only as void * — "a pointer that can point to anything" — and each module takes its own struct out of the slot with its own number (module_index). The macro that does this is ap_get_module_config() (include/http_config.h lines 553–554). We saw this design in Part 2, and the server_rec and request_rec that carry these vectors in Part 3.
Merging means combining the config of an outer block with the config of an inner block to make a new config. What wins and what gets combined is decided by each module's merge function. The core only calls it.
A pool is APR's way of managing memory: memory with the same lifetime is tied together and thrown away all at once. This post mentions pconf (the config pool), where configuration lives, and r->pool (the request pool), which lives only for one request. Part 4 has the details.
1. The road one line travels — reading and walking
A line of httpd.conf is processed in two passes. First the whole file is read into a tree (the directive tree), and then that tree is walked from the start, running each directive one by one. The pre_config hook sits between the two.
Blue is procedure, yellow is where values pile up, green is what happens on every request.
In the source, this order is plainly visible in main() — the same place we looked at in Part 5.
- Read —
ap_read_config()(server/main.cline 721) goes throughap_process_resource_config()toap_build_config()(server/config.clines 1849–1894, lines 1389–1465). The result is a tree hung on the global variableap_conftree. - The
pre_confighook — line 742. - Walk —
ap_process_config_tree()(line 748) walks the tree withap_walk_config()(server/config.clines 1961–1989). - Finish the virtual hosts — if the walk succeeds,
ap_fixup_virtual_hosts()is called right away (line 751). We get to it in section 7.
Why two passes instead of running everything at once?
Some directives do run immediately, even during the read pass: those marked EXEC_ON_READ in their directive table. LoadModule (modules/core/mod_so.c line 449), Include, <IfModule>, <IfDefine>, Define and ServerRoot (server/core.c lines 4747–4781, line 4848, lines 4873–4878) are like that. They all "change the shape of the config file itself": they pull in another file, drop some lines depending on a condition, or load a module so that new directive names become usable.
Finishing these during the read pass means that every module that will be loaded is known before the walk begins. config.c has code that relies on exactly that. The function hooked last into pre_config (APR_HOOK_REALLY_LAST) shrinks the config vectors to the real number of modules, and its comment begins "We have loaded all modules that are loaded by EXEC_ON_READ directives" (server/config.c lines 2276–2299). Only once the number of slots is fixed does the walk that fills those vectors start.
Easy to get wrong: nobody notices a typo inside a false <IfModule>
When an <IfModule> condition is false, start_cond_section() reads the lines up to the closing tag and throws them away (server/core.c lines 2942–2981). The function that does the throwing away, ap_soak_end_container(), only checks that opening and closing tags pair up; it does not look at directive names (server/config.c lines 1631–1680). Those lines never enter the tree, so they never get the name check of the walk pass either. In the last item of the example in section 5, I put the typo GreetMesage inside an <IfModule> for a module that does not exist, and httpd -t answered Syntax OK. The error only shows up once that module is loaded.
2. The tree — ap_directive_t
A node of the tree built by the read pass looks like this.
struct ap_directive_t {
/** The current directive */
const char *directive;
/** The arguments for the current directive, stored as a space
* separated list */
const char *args;
/** The next directive node in the tree */
struct ap_directive_t *next;
/** The first child node of this directive */
struct ap_directive_t *first_child;
/** The parent node of this directive */
struct ap_directive_t *parent;
/* ... */
/* ### these may go away in the future, but are needed for now */
/** The name of the file this directive was found in */
const char *filename;
/** The line number the directive was on */
int line_num;
/* ... */
};
include/util_cfgtree.h lines 46–74
One node is one line of the config file. It holds the directive name (directive), the argument string, not yet split (args), and the file name and line number where the line was. The Syntax error on line 4 of ... in error messages later comes from those last two fields.
Three pointers make the tree. The next line at the same level is next, the first line inside a section is first_child, and the way back up is parent. For a config like this
ServerName localhost
<Location /a>
GreetMessage hello-a
GreetWords a1 a2
</Location>
<VirtualHost *:8081>
ServerName one.local
<Location /b>
GreetShout On
</Location>
</VirtualHost>
the tree is:
Blue nodes are top-level lines, grey nodes are lines inside a section. Closing tags (</Location>) do not become nodes.
The place that turns one line into a node is ap_build_config_sub().
cmd_name = ap_getword_conf(temp_pool, &args);
/* ... */
newdir = apr_pcalloc(p, sizeof(ap_directive_t));
newdir->filename = parms->config_file->name;
newdir->line_num = parms->config_file->line_number;
newdir->args = apr_pstrdup(p, args);
lname = apr_pstrdup(temp_pool, cmd_name);
ap_str_tolower(lname);
ml = apr_hash_get(ap_config_hash, lname, APR_HASH_KEY_STRING);
if (ml && (cmd = ml->cmd) != NULL) {
newdir->directive = cmd->name;
if (cmd->req_override & EXEC_ON_READ) {
/* ... */
return retval;
}
}
else {
/* No known directive found? Make a copy of what we have parsed. */
newdir->directive = apr_pstrdup(p, cmd_name);
}
if (cmd_name[0] == '<') {
if (cmd_name[1] != '/') {
(*current) = ap_add_node(curr_parent, *current, newdir, 1);
}
/* ... */
}
else {
*current = ap_add_node(curr_parent, *current, newdir, 0);
}
server/config.c lines 1139–1245
- The first word is split off and taken as the directive name. Everything else goes into
argsin one piece. - The name is lower-cased and looked up in a hash (section 3). If the name is known, the node gets the spelling the module registered (
cmd->name); if not, the name is stored as written. An unknown name causes no error here. The error comes during the walk. - If it is an
EXEC_ON_READdirective, it runs on the spot and the function returns. The directive's own node is not attached to the tree; only the nodes produced by running it (for example, the contents of the fileIncluderead) are attached (lines 1168–1199). That is why the tree that gets walked contains noLoadModulenode. - A line starting with
<opens a section. The last argument1toap_add_node()means "make this node the parent and go down under it" (server/util_cfgtree.clines 20–44). A closing tag starting with</only checks that its name matches and moves one level up; it does not create a node (lines 1211–1241).
ap_getword_conf(), used to split arguments, cuts words at whitespace, but treats anything in double or single quotes as one word even if it contains spaces (server/util.c lines 835–877). That is why ErrorDocument 404 "Not here" has two arguments.
Easy to get wrong: the node for <Location /a> is named <Location
In the tree picture, the section node's name is <Location and its argument is /a>. The first word is cut at the space, so the > stays with the arguments (it is removed only when it is stuck to the end of the first word, lines 1147–1155). That is why the section functions in server/core.c all start by looking for the last > in their argument (for example dirsection() line 2519). The names registered in directive tables have no closing bracket either, like "<Directory" (section 6).
3. Finding the function by name — command_rec and ap_config_hash
The directive table a module provides
A module lists the directives it handles as an array of command_rec.
typedef struct command_struct command_rec;
struct command_struct {
/** Name of this command */
const char *name;
/** The function to be called when this directive is parsed */
cmd_func func;
/** Extra data, for functions which implement multiple commands... */
void *cmd_data;
/** What overrides need to be allowed to enable this command. */
int req_override;
/** What the command expects as arguments */
enum cmd_how args_how;
/** 'usage' message, in case of syntax errors */
const char *errmsg;
};
include/http_config.h lines 203–218
| Field | Meaning |
|---|---|
name |
The directive name |
func |
The function to call when the directive is met |
cmd_data |
An extra value passed to the function; used to tell directives apart when one function handles several |
req_override |
A set of bits saying where the directive may be used (section 5) |
args_how |
How the arguments are split before being passed on (section 4) |
errmsg |
Help text. Appended to the error message when the argument count is wrong, and printed by httpd -L |
Instead of filling this struct by hand, modules use macros like AP_INIT_TAKE1.
# define AP_INIT_TAKE1(directive, func, mconfig, where, help) \
{ directive, { .take1=func }, mconfig, where, TAKE1, help }
include/http_config.h lines 128–129
A macro is a C feature that swaps text in before compilation. The { .take1=func } part is the interesting bit. The type of the func field, cmd_func, is a union: function pointers of different shapes, one per argument style, overlaid in a single slot (lines 82–101). A TAKE1 function takes (cmd_parms *, void *, const char *); a FLAG function takes (cmd_parms *, void *, int). Because AP_INIT_FLAG fills in .flag=func, the compiler warns you if the function has the wrong shape. In the example container, I put a TAKE1-shaped function into AP_INIT_FLAG and compiled it with apxs -c; gcc printed an initialization ... from incompatible pointer type warning. The same file also has fallback definitions for compilers without designated initialisers (.name=value) (lines 158–197).
Registration — a hash keyed by the lower-case name
When a module is registered with the server (ap_add_module(), line 645), each row of its directive table goes into a hash.
while (cmd && cmd->name) {
mln = apr_palloc(tpool, sizeof(ap_mod_list));
mln->cmd = cmd;
mln->m = m;
dir = apr_pstrdup(tpool, cmd->name);
ap_str_tolower(dir);
mln->next = apr_hash_get(ap_config_hash, dir, APR_HASH_KEY_STRING);
apr_hash_set(ap_config_hash, dir, APR_HASH_KEY_STRING, mln);
++cmd;
}
The key is the name in lower case, and the value is a small list (ap_mod_list) saying "which module, which command_rec". If the name is already there, the new entry goes to the front of the list (lines 549–550). The lower-case key is why directive names are case-insensitive: GREETMESSAGE hi is found too.
The walk — one hash lookup per node
The body of the walk is short. ap_walk_config() just follows next and calls ap_walk_config_sub() for each node (lines 1362–1387). It does not step into sections; the section directive's function does that itself (section 6).
char *dir = apr_pstrdup(parms->temp_pool, current->directive);
ap_str_tolower(dir);
ml = apr_hash_get(ap_config_hash, dir, APR_HASH_KEY_STRING);
if (ml == NULL) {
/* ... */
return apr_pstrcat(parms->pool, "Invalid command '",
current->directive,
"', perhaps misspelled or defined by a module "
"not included in the server configuration",
NULL);
/* ... */
}
for ( ; ml != NULL; ml = ml->next) {
void *dir_config = ap_set_config_vectors(parms->server,
section_vector,
parms->path,
ml->m,
parms->pool);
const char *retval;
cmd = ml->cmd;
/* Once was enough? */
if (cmd->req_override & EXEC_ON_READ) {
continue;
}
retval = invoke_cmd(cmd, parms, dir_config, current->args, NULL);
if (retval != NULL && strcmp(retval, DECLINE_CMD) != 0) {
/* ... */
return retval;
}
}
return NULL;
server/config.c lines 1306–1359
The three red boxes are the three places a config error can come from. The yellow boxes are the hash and the config vector — where things are stored.
ap_set_config_vectors() inside the for loop matters. It takes this module's slot out of the config vector of the section being walked (section_vector) and returns it — and if the slot is empty, it calls create_dir_config right there to fill it. While it is at it, if the current server's server-config slot is empty too, it fills that with create_server_config (lines 1084–1104). The directory config obtained this way is passed as the module function's second argument, mconfig. From the module function's point of view, it receives "my directory config for the section being read".
Easy to get wrong
- Finding is a single hash lookup.
config.calso hasap_find_command_in_modules(), which scans the module list from the start (lines 1068–1082), but at this commit the only caller is the name check forAllowOverrideList(server/core.cline 2030). - If two modules register the same name, both are called. The
forloop above stops only when a function returns an error; on success (NULL) it moves on to the next module in the list. The special stringDECLINE_CMD, which a function returns to say "I won't handle this", is not treated as an error either (include/http_config.hline 264). I checked this part in the source only; I did not run an example for it. EXEC_ON_READdirectives are skipped by the walk ("Once was enough?"), because they already ran during the read pass.
4. invoke_cmd() — splitting the arguments and calling the module function
The function that actually calls the command_rec found in the hash is invoke_cmd() (lines 853–1053). The first part is the context check (section 5); the rest is a big switch on args_how. Here are just two of the cases.
case TAKE1:
w = ap_getword_conf(parms->pool, &args);
if (*w == '\0' || *args != 0)
return apr_pstrcat(parms->pool, cmd->name, " takes one argument",
cmd->errmsg ? ", " : NULL, cmd->errmsg, NULL);
return cmd->AP_TAKE1(parms, mconfig, w);
/* ... */
case FLAG:
/*
* This is safe to use temp_pool here, because the 'flag' itself is not
* forwarded as-is
*/
w = ap_getword_conf(parms->temp_pool, &args);
if (*w == '\0' || (ap_cstr_casecmp(w, "on") && ap_cstr_casecmp(w, "off")))
return apr_pstrcat(parms->pool, cmd->name, " must be On or Off",
NULL);
return cmd->AP_FLAG(parms, mconfig, ap_cstr_casecmp(w, "off") != 0);
server/config.c lines 922–1046
The point is that the core checks the argument count and On/Off for you; the module function receives words that are already split. ITERATE, in the part left out, calls the same function several times, once per word (lines 998–1015), and FLAG passes 1 for On and 0 for Off (case-insensitive). There are thirteen styles in all (include/http_config.h lines 49–67).
The module function only ever receives arguments that are already split and checked. The error sentences are exactly the httpd -t output in section 5's example.
args_how |
What the module function receives | Real examples (this commit) |
|---|---|---|
RAW_ARGS |
The unsplit string as is | <Directory, AllowOverride (core.c 4729, 4801) |
TAKE1 |
One word | DocumentRoot (core.c 4797) |
TAKE2 / TAKE12 |
Two words / one or two | ErrorDocument (4799), LoadModule (mod_so.c 449) / Define (4778) |
TAKE3, TAKE23, TAKE123, TAKE13 |
Up to three words; missing ones are NULL |
|
ITERATE |
One word at a time, called once per word | LogLevel (4881) |
ITERATE2 |
The first word plus each remaining word, called repeatedly | AddIcon (mod_autoindex.c 571) |
FLAG |
1 for On, 0 for Off |
KeepAlive (http_core.c 112) |
NO_ARGS |
Nothing | |
TAKE_ARGV |
argc, argv (up to 64) |
AllowOverrideList (4804) |
The cmd_parms a module function receives
The first argument of every directive function is a cmd_parms *, a struct describing "where we are reading right now" (include/http_config.h lines 288–334). Here are the fields you will use most.
| Field | Holds |
|---|---|
server |
The server being configured. Inside <VirtualHost>, that virtual host's server_rec |
path |
Inside <Directory> or <Location>, that path; outside, NULL |
override |
The bits allowed at the current spot (section 5) |
directive |
The tree node being processed |
pool, temp_pool |
The pool where config values live (pconf) and a scratch pool thrown away once configuration is done |
info |
The command_rec's cmd_data (set at line 887) |
On success a function returns NULL; on failure it returns an error message string — a common convention in C, which has no exceptions. That string travels back up the walk, and ap_process_config_tree() prints it on the line after Syntax error on line %d of %s: (lines 1977–1986). The line number is the line_num stored in the node back in section 2.
Example: a module with four directives
For this post I wrote a small module called mod_greet. Each of its four directives has a different argument style and a different rule about where it may appear.
| Directive | Style | Where (req_override) |
Config kind | Merge rule |
|---|---|---|---|---|
GreetMessage |
TAKE1 |
OR_FILEINFO |
Directory | The inner one if set |
GreetShout |
FLAG |
ACCESS_CONF |
Directory | The inner one if set |
GreetWords |
ITERATE |
OR_FILEINFO |
Directory | Inner words appended after outer ones |
GreetTag |
TAKE1 |
RSRC_CONF |
Server | The inner one if set |
A request to a URL with SetHandler greet makes the handler print the values in effect and which sections were merged (trail). On top of that, every time a create or merge function is called it writes a line to /tmp/greet.trace, using ap_state_query() to note whether the server is starting up or handling requests (boot#1 is the first config read, boot#2 the second, request is while serving requests; why the config is read twice is in Part 5).
/* An example written for this post (not part of the httpd source) */
#include "httpd.h"
#include "http_config.h"
#include "http_core.h"
#include "http_protocol.h"
#include "apr_strings.h"
#include <stdio.h>
#include <unistd.h>
module AP_MODULE_DECLARE_DATA greet_module;
/* Directory config: one per <Directory>/<Location>/.htaccess */
typedef struct {
const char *label; /* which section created it (the dir of create_dir_config) */
const char *message; /* GreetMessage — NULL means "not set" */
int shout; /* GreetShout — -1 means "not set" */
apr_array_header_t *words; /* GreetWords — appended when merging */
const char *trail; /* records which sections were merged */
} greet_dir_conf;
/* Server config: one for the main server and one per <VirtualHost> */
typedef struct {
const char *tag; /* GreetTag */
} greet_srv_conf;
/* ... trace(): writes one line to /tmp/greet.trace ... */
static void *create_dir(apr_pool_t *p, char *dir)
{
greet_dir_conf *c = apr_pcalloc(p, sizeof(*c));
c->label = dir ? apr_pstrdup(p, dir) : "(server)";
c->shout = -1;
c->words = apr_array_make(p, 2, sizeof(const char *));
c->trail = c->label;
trace("create_dir_config dir=%s%s", c->label, "");
return c;
}
static void *merge_dir(apr_pool_t *p, void *basev, void *addv)
{
greet_dir_conf *base = basev, *add = addv;
greet_dir_conf *c = apr_pcalloc(p, sizeof(*c));
c->label = add->label;
/* override: the inner section's (add) value if set, otherwise the outer (base) one */
c->message = add->message ? add->message : base->message;
c->shout = add->shout != -1 ? add->shout : base->shout;
/* append: the inner words go after the outer ones */
c->words = apr_array_append(p, base->words, add->words);
c->trail = apr_pstrcat(p, base->trail, " + ", add->trail, NULL);
trace("merge_dir_config base=[%s] add=%s", base->trail, add->label);
return c;
}
/* ... create_srv(), merge_srv(): for the server config; handle the one tag the same way ... */
/* TAKE1: one argument */
static const char *set_message(cmd_parms *cmd, void *mconfig, const char *arg)
{
greet_dir_conf *c = mconfig; /* the directory config of the section being read */
c->message = arg;
return NULL; /* NULL = success, a string = error message */
}
/* FLAG: 1 for On, 0 for Off */
static const char *set_shout(cmd_parms *cmd, void *mconfig, int on)
{
((greet_dir_conf *)mconfig)->shout = on;
return NULL;
}
/* ITERATE: this function is called once per argument */
static const char *add_word(cmd_parms *cmd, void *mconfig, const char *w)
{
greet_dir_conf *c = mconfig;
*(const char **)apr_array_push(c->words) = w;
return NULL;
}
/* The server config comes from cmd->server, not from mconfig */
static const char *set_tag(cmd_parms *cmd, void *mconfig, const char *arg)
{
greet_srv_conf *c = ap_get_module_config(cmd->server->module_config,
&greet_module);
c->tag = arg;
return NULL;
}
static const command_rec greet_cmds[] = {
AP_INIT_TAKE1("GreetMessage", set_message, NULL, OR_FILEINFO,
"the greeting text"),
AP_INIT_FLAG("GreetShout", set_shout, NULL, ACCESS_CONF,
"On to print the greeting in upper case"),
AP_INIT_ITERATE("GreetWords", add_word, NULL, OR_FILEINFO,
"one or more extra words"),
AP_INIT_TAKE1("GreetTag", set_tag, NULL, RSRC_CONF,
"a tag for this server"),
{ NULL }
};
static int greet_handler(request_rec *r)
{
/* ... */
d = ap_get_module_config(r->per_dir_config, &greet_module);
s = ap_get_module_config(r->server->module_config, &greet_module);
/* ... prints d and s as text/plain (message in upper case if shout is 1) ... */
}
/* ... register_hooks(): hooks greet_handler into the handler hook ... */
AP_DECLARE_MODULE(greet) = {
STANDARD20_MODULE_STUFF,
create_dir, /* create_dir_config */
merge_dir, /* merge_dir_config */
create_srv, /* create_server_config */
merge_srv, /* merge_server_config */
greet_cmds, /* the directive table */
register_hooks
};
D:\openSource\httpd-examples\ex\u07-directives\mod_greet.c (the parts shortened with /* ... */ are the trace helper, the server-config create and merge functions, and the output code; the comments in the original file are in Korean)
Two things to notice. The directory-config functions (set_message and friends) write into the mconfig they receive, but the server-config function set_tag ignores mconfig and takes the server config straight from cmd->server. That is because what the core passes to the function is always a directory config (the dir_config from section 3). Also, "not set" is marked with NULL and -1, because the merge function needs to know "did the inner section set this value?".
This example was run in a Docker container (Ubuntu 24.04) on httpd 2.5.1-dev built as-is from the reference commit (e4af30fe6d). run.sh compiles it with apxs -c, starts the server with the config below, and sends requests with curl (/tmp/head.conf holds the common lines such as the LoadModule lines and ServerName localhost).
Include /tmp/head.conf
Listen 8080
Listen 8081
DocumentRoot /tmp/www
GreetMessage hello-from-server
GreetWords s1
GreetTag main-tag
<Location />
SetHandler greet
</Location>
<Location /a>
GreetMessage hello-a
GreetWords a1 a2
</Location>
<Location /a/b>
GreetMessage hello-b
GreetShout On
</Location>
<Location /x/y>
GreetMessage from-x-y
</Location>
<Location /x>
GreetMessage from-x
</Location>
<Directory /tmp/www/d>
GreetMessage from-directory
</Directory>
<Directory /tmp/www/ht>
AllowOverride FileInfo
</Directory>
<Directory /tmp/www/ind>
AllowOverride Indexes
</Directory>
<VirtualHost *:8081>
ServerName one.local
GreetTag one-tag
</VirtualHost>
<VirtualHost *:8081>
ServerName two.local
</VirtualHost>
First, httpd -L. This option prints the directive tables of the loaded modules as they are (server/config.c lines 2413–2504), so you can see how the req_override bits read in plain words.
### httpd -L (Greet only)
GreetMessage (mod_greet.c)
the greeting text
Allowed in *.conf anywhere and in .htaccess
when AllowOverride includes FileInfo
GreetShout (mod_greet.c)
On to print the greeting in upper case
Allowed in *.conf only inside <Directory>, <Files>, <Location>, or <If>
GreetWords (mod_greet.c)
one or more extra words
Allowed in *.conf anywhere and in .htaccess
when AllowOverride includes FileInfo
GreetTag (mod_greet.c)
a tag for this server
Allowed in *.conf only outside <Directory>, <Files>, <Location>, or <If>
The rest of the output is split between section 5 (errors) and section 7 (merging).
5. Where may it go? — req_override and parms->override
An AND of two sets of bits
The value in a command_rec's req_override is made of bits like these.
#define OR_NONE 0 /**< *.conf is not available anywhere in this override */
#define OR_LIMIT 1 /**< *.conf inside <Directory> or <Location>
and .htaccess when AllowOverride Limit */
#define OR_OPTIONS 2 /**< *.conf anywhere
and .htaccess when AllowOverride Options */
#define OR_FILEINFO 4 /**< *.conf anywhere
and .htaccess when AllowOverride FileInfo */
#define OR_AUTHCFG 8 /**< *.conf inside <Directory> or <Location>
and .htaccess when AllowOverride AuthConfig */
#define OR_INDEXES 16 /**< *.conf anywhere
and .htaccess when AllowOverride Indexes */
#define OR_UNSET 32 /**< bit to indicate that AllowOverride has not been set */
#define ACCESS_CONF 64 /**< *.conf inside <Directory> or <Location> */
#define RSRC_CONF 128 /**< *.conf outside <Directory> or <Location> */
#define EXEC_ON_READ 256 /**< force directive to execute a command
which would modify the configuration (like including another
file, or IFModule */
/* ... */
/** this directive can be placed anywhere */
#define OR_ALL (OR_LIMIT|OR_OPTIONS|OR_FILEINFO|OR_AUTHCFG|OR_INDEXES)
include/http_config.h lines 228–255
(The < is an angle bracket written for the documentation generator; that is how it appears in the source.) The OR_ in the names stands for override, and each one pairs with a word of AllowOverride: Limit, Options, FileInfo, AuthConfig, Indexes.
Meanwhile the walking side carries, in cmd_parms's override, "the bits allowed at this very spot". The first thing invoke_cmd() does is compare the two.
if ((parms->override & cmd->req_override) == 0 && !override_list_ok) {
if (parms->override & NONFATAL_OVERRIDE) {
/* ... */
return NULL;
}
else if (parms->directive && parms->directive->parent) {
return apr_pstrcat(parms->pool, cmd->name, " not allowed in ",
parms->directive->parent->directive, ">",
" context", NULL);
}
else {
return apr_pstrcat(parms->pool, cmd->name,
" not allowed here", NULL);
}
}
If the bitwise AND (&) is 0 — no bit in common — it is an error. If even one bit overlaps, the directive passes. If the node has a parent (it is inside a section) the message is not allowed in <section> context; otherwise it is not allowed here. The part shortened with /* ... */ is a call that logs a warning: when Nonfatal=Override has been added to AllowOverride in a <Directory>, this is the path that downgrades such an error in .htaccess to a one-line warning and moves on.
So what is parms->override at each spot?
| Spot | parms->override |
Source |
|---|---|---|
| Top level of the config file | (RSRC_CONF | OR_ALL) & ~(OR_AUTHCFG | OR_LIMIT) = RSRC_CONF, OR_OPTIONS, OR_FILEINFO, OR_INDEXES |
config.c line 1973 |
Inside <VirtualHost> |
Same as top level (only cmd->server changes) |
core.c lines 3028–3079 |
Inside <Directory> or <Location> |
OR_ALL | ACCESS_CONF (RSRC_CONF is gone) |
core.c line 2543, line 2659 |
.htaccess |
The AllowOverride bits in effect for that directory |
config.c line 2022 |
Hold mod_greet's directives up against this table one by one and you get exactly the sentences httpd -L printed. GreetTag (RSRC_CONF) is blocked inside sections, GreetShout (ACCESS_CONF) is blocked outside them, and GreetMessage (OR_FILEINFO) passes everywhere. Here are the real errors. run.sh writes one broken config file at a time and checks it with httpd -t (the numbers on the left are line numbers in the config file).
The per-place bits from the table above held against the bits in the directive table. The error sentences come from the httpd -t output below; Require inside Location is worked out from the bits (OR_ALL includes OR_AUTHCFG).
### Require at the top level (mod_authz_core, OR_AUTHCFG)
3 Require all granted
AH00526: Syntax error on line 3 of /tmp/bad.conf:
Require not allowed here
### GreetShout at the top level
3 GreetShout On
AH00526: Syntax error on line 3 of /tmp/bad.conf:
GreetShout not allowed here
### GreetShout inside VirtualHost
3 <VirtualHost *:8090>
4 GreetShout On
5 </VirtualHost>
AH00526: Syntax error on line 4 of /tmp/bad.conf:
GreetShout not allowed in <VirtualHost> context
### GreetTag inside Location
3 <Location /a>
4 GreetTag t
5 </Location>
AH00526: Syntax error on line 4 of /tmp/bad.conf:
GreetTag not allowed in <Location> context
### GreetShout yes
3 <Location /a>
4 GreetShout yes
5 </Location>
AH00526: Syntax error on line 4 of /tmp/bad.conf:
GreetShout must be On or Off
### GreetMessage with two arguments
3 GreetMessage hello world
AH00526: Syntax error on line 3 of /tmp/bad.conf:
GreetMessage takes one argument, the greeting text
### a typo in the name
3 GreetMesage hello
AH00526: Syntax error on line 3 of /tmp/bad.conf:
Invalid command 'GreetMesage', perhaps misspelled or defined by a module not included in the server configuration
### a typo inside a false IfModule
3 <IfModule nosuch_module>
4 GreetMesage hello
5 </IfModule>
Syntax OK
The first one is an error that anyone who has edited httpd.conf has probably hit at least once. Require is registered as OR_AUTHCFG (modules/aaa/mod_authz_core.c line 688), and that is precisely the bit removed from the top-level parms->override. The the greeting text at the end of the GreetMessage hello world message is the help text (errmsg) from the directive table.
A second check: ap_check_cmd_context()
The bit test alone cannot express a rule like "not inside <VirtualHost>", because parms->override inside <VirtualHost> is the same as at the top level. Rules like that are checked by the directive function itself, by calling ap_check_cmd_context(). Pass it flags such as NOT_IN_VIRTUALHOST, NOT_IN_DIRECTORY, NOT_IN_LOCATION or NOT_IN_HTACCESS and it decides by looking at cmd->server->is_virtual or at the parent nodes in the tree (server/core.c lines 1324–1382; the flags are defined in include/http_config.h lines 969–981). <VirtualHost> passes GLOBAL_ONLY to this function to keep itself out of other sections (line 3036).
How it tells that it is inside .htaccess is a neat trick: it checks whether cmd->pool == cmd->temp_pool (server/core.c line 1343). Only the .htaccess reader, which we will see below, puts the same request pool into both pool slots.
AllowOverride sets parms->override for .htaccess
AllowOverride is itself an ACCESS_CONF directive, so it can only be used inside a section (server/core.c line 4801). Of those, only <Directory> gives it any meaning: inside <Location> it is not an error, but it logs a "Useless use of AllowOverride" warning (lines 1884–1887). All its function set_override() does is turn the words into bits and store them in the core's directory config: FileInfo becomes OR_FILEINFO, All becomes OR_ALL, None becomes OR_NONE (0) (lines 1876–1951).
When a request comes in, ap_directory_walk(), which goes down the directories, calls ap_parse_htaccess() with those bits (server/request.c lines 1096–1105), and ap_parse_htaccess() puts them straight into parms.override and processes .htaccess with the same two passes as the config file: ap_build_config() and ap_walk_config().
parms.config_file = f;
errmsg = ap_build_config(&parms, r->pool, r->pool, &temptree);
if (errmsg == NULL)
errmsg = ap_walk_config(temptree, &parms, dc);
ap_cfg_closefile(f);
if (errmsg) {
ap_log_rerror(APLOG_MARK, APLOG_ALERT, 0, r,
"%s: %s", filename, errmsg);
return HTTP_INTERNAL_SERVER_ERROR;
}
server/config.c lines 2044–2055
An error in .htaccess does not stop the server; it ends just that request with a 500. In the example, three directories each have an .htaccess containing the same GreetMessage from-htaccess.
### curl http://127.0.0.1:8080/ht/
uri=/ht/
message=from-htaccess
words=s1,h1
tag=main-tag
trail=(server) + /tmp/www/ht/
### curl -i http://127.0.0.1:8080/ind/
HTTP/1.1 500 Internal Server Error
### curl -i http://127.0.0.1:8080/none/
HTTP/1.1 500 Internal Server Error
### error_log
[Sat Oct 24 12:17:08.494971 2026] [core:alert] [pid 162:tid 167] [client 127.0.0.1:55322] /tmp/www/ind/.htaccess: GreetMessage not allowed here
[Sat Oct 24 12:17:08.503381 2026] [core:alert] [pid 163:tid 219] [client 127.0.0.1:55336] /tmp/www/none/.htaccess: GreetMessage not allowed here
(Only the parts of the run.sh output for these three requests are gathered here. The time, pid, tid and client port differ on every run.) /ht/ has AllowOverride FileInfo, so GreetMessage, which is OR_FILEINFO, passed (the .htaccess in /ht/ also has a GreetWords h1 line). /ind/ has AllowOverride Indexes, so there is no bit in common. Directives in .htaccess have no parent node, so the message is not allowed here.
Easy to get wrong: not writing AllowOverride at all is not the same as None
/none/ is a directory for which AllowOverride is never written anywhere. Even so, its .htaccess was read, every directive in it was refused, and the result was a 500. The reason is the initial value of the core's directory config: create_core_dir_config() starts override at OR_UNSET|OR_NONE, which is 32 (server/core.c line 170). The directory walk skips .htaccess only when this value is 0 (server/request.c lines 1096–1098), so 32 means "read the file, but allow no directive at all". Only writing AllowOverride None makes it 0, and then the file is not even opened. That is also why it matters that the shipped default httpd.conf writes AllowOverride none in <Directory /> (docs/conf/httpd.conf.in lines 115–118). The same spots in core.c and request.c on the 2.4.x branch have the same code (I did not run it on 2.4.x).
6. Sections — <Directory>, <Location> and <VirtualHost> are core directives
Sections are not special syntax either. They are RAW_ARGS directives in the core module's directive table.
AP_INIT_RAW_ARGS("<Directory", dirsection, NULL, RSRC_CONF,
"Container for directives affecting resources located in the specified "
"directories"),
AP_INIT_RAW_ARGS("<Location", urlsection, NULL, RSRC_CONF,
"Container for directives affecting resources accessed through the "
"specified URL paths"),
AP_INIT_RAW_ARGS("<VirtualHost", virtualhost_section, NULL, RSRC_CONF,
"Container to map directives to a particular virtual host, takes one or "
"more host addresses"),
All three sections are RSRC_CONF. So if you put a <Directory> inside a <Directory>, the bit test of section 5 catches it (inside a section, RSRC_CONF is gone). Section functions follow the same pattern: change a few fields of cmd_parms, call ap_walk_config() again starting from their own node's first_child, then put the fields back. This is how the walk recurses into sections.
static const char *dirsection(cmd_parms *cmd, void *mconfig, const char *arg)
{
const char *errmsg;
const char *endp = ap_strrchr_c(arg, '>');
int old_overrides = cmd->override;
char *old_path = cmd->path;
ap_regex_t *old_regex = cmd->regex;
core_dir_config *conf;
ap_conf_vector_t *new_dir_conf = ap_create_per_dir_config(cmd->pool);
const command_rec *thiscmd = cmd->cmd;
/* ... */
cmd->path = ap_getword_conf(cmd->pool, &arg);
cmd->override = OR_ALL|ACCESS_CONF;
/* ... */
/* initialize our config and fetch it */
conf = ap_set_config_vectors(cmd->server, new_dir_conf, cmd->path,
&core_module, cmd->pool);
errmsg = ap_walk_config(cmd->directive->first_child, cmd, new_dir_conf);
if (errmsg != NULL)
return errmsg;
/* ... */
ap_add_per_dir_conf(cmd->server, new_dir_conf);
/* ... */
cmd->path = old_path;
cmd->override = old_overrides;
cmd->regex = old_regex;
return NULL;
}
- It creates an empty config vector (
ap_create_per_dir_config(), an array with every slotNULL,server/config.clines 366–369). - It puts the path into
cmd->pathand the in-section bits intocmd->override. Thispathis thedirpassed tocreate_dir_config(p, dir)for the directives inside. Thecreate_dir_config dir=/tmp/www/d/in the example's trace is that value; the trailing/was added by this function (lines 2587–2588). - It walks the nodes inside the section against this vector. Every directive inside writes its value into this vector's slots.
- It stores the filled vector in
sec_dir, an array in the server's core config (for<Location>,sec_url; lines 639–653). When a request comes in, these arrays are scanned to pick the matching sections. - It restores the fields it changed. Once you leave the section, the outer rules apply again.
The five steps above, in dirsection()'s actual order. The path comes from the example trace's create_dir_config dir=/tmp/www/d/.
urlsection() (<Location>) is almost the same (lines 2633–2709). The difference is that it does not normalise the path as a filesystem path.
<VirtualHost> changes a different field. It creates a new server_rec with ap_init_virtual_host(), links it into the server list, switches cmd->server to it, and walks the inside against that server's lookup_defaults.
errmsg = ap_init_virtual_host(p, arg, main_server, &s);
if (errmsg) {
return errmsg;
}
s->next = main_server->next;
main_server->next = s;
s->defn_name = cmd->directive->filename;
s->defn_line_number = cmd->directive->line_num;
cmd->server = s;
errmsg = ap_walk_config(cmd->directive->first_child, cmd,
s->lookup_defaults);
cmd->server = main_server;
lookup_defaults is the server_rec's vector of "this server's default directory config" (include/httpd.h lines 1424–1426). The vector passed when the walk starts at the top level is the main server's lookup_defaults too (line 1977). That is why the GreetMessage hello-from-server written outside any section lands in the main server's lookup_defaults. A new virtual host's module_config and lookup_defaults start as vectors with every slot empty (server/config.c lines 2118–2119). When they inherit the main server's values is the topic of the next section.
Easy to get wrong: <Location> sections apply in the order written, not by length
In the example config, <Location /x/y> is written before <Location /x>. A request for /x/y/z matches both.
### curl http://127.0.0.1:8080/x/y/z
uri=/x/y/z
message=from-x
words=s1
tag=main-tag
trail=(server) + /x/y + /x
You might expect the more specific /x/y to win, but the result is from-x. As trail shows, /x/y was merged first and /x was merged on top of it. <Directory> sections are sorted at startup: non-regex ones first, and within those, fewer path components (fewer /) first (ap_core_reorder_directories() and its comparison function reorder_sorter(), server/core.c lines 709–784). But that function sorts only sec_dir; it leaves sec_url alone. When a request comes in, ap_location_walk() scans the array from the front and merges each match in turn (server/request.c lines 1561–1649). That is why it pays to write broad <Location> sections above narrow ones.
7. Merging — when, and by whom
Creating: at startup, one per section
Here is the trace right after the example server starts.
### 2) startup trace (/tmp/greet.trace)
[boot#1 pid=158] create_server_config main
[boot#1 pid=158] create_dir_config dir=(server)
[boot#1 pid=158] create_dir_config dir=/a
[boot#1 pid=158] create_dir_config dir=/a/b
[boot#1 pid=158] create_dir_config dir=/x/y
[boot#1 pid=158] create_dir_config dir=/x
[boot#1 pid=158] create_dir_config dir=/tmp/www/d/
[boot#1 pid=158] create_dir_config dir=(server)
[boot#1 pid=158] create_server_config vhost
[boot#1 pid=158] merge_server_config base=main-tag add=one-tag
[boot#1 pid=158] merge_dir_config base=[(server)] add=(server)
[boot#2 pid=158] create_server_config main
[boot#2 pid=158] create_dir_config dir=(server)
[boot#2 pid=159] create_dir_config dir=/a
[boot#2 pid=159] create_dir_config dir=/a/b
[boot#2 pid=159] create_dir_config dir=/x/y
[boot#2 pid=159] create_dir_config dir=/x
[boot#2 pid=159] create_dir_config dir=/tmp/www/d/
[boot#2 pid=159] create_dir_config dir=(server)
[boot#2 pid=159] create_server_config vhost
[boot#2 pid=159] merge_server_config base=main-tag add=one-tag
[boot#2 pid=159] merge_dir_config base=[(server)] add=(server)
(The pids differ on every run.) The same batch appears twice, as boot#1 and boot#2, because the config is read twice (Part 5). Reading one batch from the top:
- The main server's server config and default directory config are created before the file is even read.
init_server_config()callscreate_server_configandcreate_dir_config(p, NULL)for every loaded module (server/config.clines 2262–2263, lines 270–283, lines 311–322).dirisNULL, which the example prints as(server). - A section's directory config is created during the walk, the first time a directive of this module is met inside that section (the
ap_set_config_vectors()from section 3).<Location />is not in the trace: it contains onlySetHandler(a core directive), sogreet's slot there staysNULLto the end. - When the walk meets
GreetTaginside the virtual hostone.local, a directory config (dir=(server)) appears in that server'slookup_defaultsand a server config (vhost) in itsmodule_config.two.localhas nogreetdirective, so nothing is created for it. - The last two lines are the merges done at startup.
In the middle of boot#2 the pid changes from 158 to 159. The main server's two configs are created before the file is read and the sections' configs during the walk; in between, in the pre_config hook, the event MPM detaches into a background process on the second startup pass (apr_proc_detach(), server/mpm/event/event.c lines 4024–4040). That moment was covered in detail in section 4 of Part 5. Here, just take it as evidence that "section configs are created during the walk, after pre_config".
Merging server configs: once, at startup
ap_fixup_virtual_hosts() hands the main server's config down to each virtual host (server/config.c lines 2170–2211). Server configs are combined with merge_server_configs(), and the default directory configs (lookup_defaults) with ap_merge_per_dir_configs() (lines 2178–2182). Here is the server-config side.
static void merge_server_configs(apr_pool_t *p, ap_conf_vector_t *base,
server_rec *virt)
{
/* Can reuse the 'virt' vector for the spine of it, since we don't
* have to deal with the moral equivalent of .htaccess files here...
*/
void **base_vector = (void **)base;
void **virt_vector = (void **)virt->module_config;
module *modp;
for (modp = ap_top_module; modp; modp = modp->next) {
merger_func df = modp->merge_server_config;
int i = modp->module_index;
if (!virt_vector[i]) {
if (df && modp->create_server_config
&& (ap_get_module_flags(modp) &
AP_MODULE_FLAG_ALWAYS_MERGE)) {
virt_vector[i] = (*modp->create_server_config)(p, virt);
}
else {
virt_vector[i] = base_vector[i];
df = NULL;
}
}
if (df) {
virt_vector[i] = (*df)(p, base_vector[i], virt_vector[i]);
}
}
}
If the virtual host's slot is empty, the merge function is not called and the main server's pointer is simply copied. The only exception is a module that has set AP_MODULE_FLAG_ALWAYS_MERGE, which gets an empty config created and merged. If the slot is filled, the merge function is called.
Yellow boxes are config structs that were actually created; the grey box is an empty slot.
Here are the results of requesting the same URL on the two virtual hosts.
### curl -H 'Host: one.local' http://127.0.0.1:8081/a/page
uri=/a/page
message=hello-a
words=s1,a1,a2
tag=one-tag
trail=(server) + (server) + /a
### curl -H 'Host: two.local' http://127.0.0.1:8081/a/page
uri=/a/page
message=hello-a
words=s1,a1,a2
tag=main-tag
trail=(server) + /a
This matches the startup trace, where merge_server_config was called only once (for one.local). The two (server)s in one.local's trail are the trace of that virtual host's lookup_defaults being merged with the main server's (the last line of the startup trace). two.local inherited the main server's lookup_defaults slot pointer and all. Both hosts got the value from <Location /a> because the core module's server-config merge function appends the virtual host's <Directory> and <Location> lists after the main server's (merge_core_server_configs(), server/core.c lines 552–553). The core, too, is just one module following the same "module's merge function" rule we have been looking at.
Merging directory configs: on every request
On the core side, there is exactly one function that combines directory configs.
AP_CORE_DECLARE(ap_conf_vector_t *) ap_merge_per_dir_configs(apr_pool_t *p,
ap_conf_vector_t *base,
ap_conf_vector_t *new_conf)
{
void **conf_vector = apr_palloc(p, sizeof(void *) * conf_vector_length);
void **base_vector = (void **)base;
void **new_vector = (void **)new_conf;
int i;
for (i = 0; i < total_modules; i++) {
if (!new_vector[i]) {
conf_vector[i] = base_vector[i];
}
else {
const merger_func df = merger_func_cache[i];
if (df && base_vector[i]) {
conf_vector[i] = (*df)(p, base_vector[i], new_vector[i]);
}
else
conf_vector[i] = new_vector[i];
}
}
return (ap_conf_vector_t *)conf_vector;
}
For each module there are three cases. If the inner section's (new) slot is empty, the outer one is used as is; if both are present, the module's merge function is called; if the outer one is empty, the inner one is used as is. The result is a newly allocated vector, and the two original vectors are left untouched. p is the request pool, so the merged result disappears along with the request.
The callers are the walk functions in server/request.c. While a request is processed, r->per_dir_config starts from the server's lookup_defaults (line 290); the core's map_to_storage hook merges <Directory>, .htaccess and <Files> with ap_directory_walk() and ap_file_walk() (server/core.c lines 5100–5104); and then <Location> is merged (server/request.c lines 292–301). Because <Location> is merged after <Directory>, for a directive that uses an "inner one if set" rule like the example's, <Location> wins when both set the same value. Where these steps sit in request processing is covered in detail in Part 10.
The <Location> merge happens in two steps. The matching sections are first merged with each other (now_merged), and then that result is merged on top of the r->per_dir_config so far.
if (now_merged) {
now_merged = ap_merge_per_dir_configs(r->pool,
now_merged,
sec_ent[sec_idx]);
}
else {
now_merged = sec_ent[sec_idx];
}
/* ... */
if (now_merged) {
r->per_dir_config = ap_merge_per_dir_configs(r->pool,
r->per_dir_config,
now_merged);
}
server/request.c lines 1637–1684
The yellow box is config created at startup; the blue boxes are merges that happen on every request.
Here are the example's responses and the trace at that time.
### curl http://127.0.0.1:8080/hello
uri=/hello
message=hello-from-server
words=s1
tag=main-tag
trail=(server)
### curl http://127.0.0.1:8080/a/page
uri=/a/page
message=hello-a
words=s1,a1,a2
tag=main-tag
trail=(server) + /a
### curl http://127.0.0.1:8080/a/b/c
uri=/a/b/c
message=HELLO-B
words=s1,a1,a2
tag=main-tag
trail=(server) + /a + /a/b
### curl http://127.0.0.1:8080/d/
uri=/d/
message=from-directory
words=s1
tag=main-tag
trail=(server) + /tmp/www/d/
### 3) trace while serving requests
[request pid=163] merge_dir_config base=[(server)] add=/a
[request pid=163] merge_dir_config base=[/a] add=/a/b
[request pid=163] merge_dir_config base=[(server)] add=/a/b
[request pid=163] merge_dir_config base=[/x/y] add=/x
[request pid=163] merge_dir_config base=[(server)] add=/x
[request pid=163] merge_dir_config base=[(server)] add=/tmp/www/d/
[request pid=163] create_dir_config dir=/tmp/www/ht/
[request pid=163] merge_dir_config base=[(server)] add=/tmp/www/ht/
### the same two URLs once more (/a/b/c, /ht/)
[request pid=163] merge_dir_config base=[/a] add=/a/b
[request pid=163] merge_dir_config base=[(server)] add=/a/b
[request pid=163] create_dir_config dir=/tmp/www/ht/
[request pid=163] merge_dir_config base=[(server)] add=/tmp/www/ht/
(Only the needed parts of the run.sh output are gathered here. The pids differ on every run, and on a rerun the second /a/b/c request sometimes went to a different child process; either way the merge happens again. The responses for /x/y/z and /ht/ are in sections 6 and 5. The trace under "3)" covers the six requests from /hello to /ht/. A merged config inherits the inner section's label, which is why it shows up as add=/a/b.)
- The two lines for
/a/b/care exactly the picture above./aand/a/bwere merged first (base=[/a] add=/a/b), and that was merged on top of(server). Formessagethe inner section won (hello-b, upper-cased because ofGreetShout On), whilewordswere appended by the merge function, givings1,a1,a2. The difference between those two values shows that which value wins is decided by the module's merge function, not by the core. /hellohas no merge line at all.<Location />did match, butgreet's slot in that vector isNULL, soap_merge_per_dir_configs()did not call the merge function and used the outer one as is.- Requesting the same URL again merges again. The merged result lives in the request pool and disappears when the request ends. The walk functions do have a cache, but it sits in a place that belongs to a single request (a note slot in
r->request_config,server/core.clines 5452–5467), so it is reused only within that request and its internal redirects or subrequests (server/request.clines 470–520). .htaccessis read again on every request. Each request for/ht/printedcreate_dir_config dir=/tmp/www/ht/again. The cache inap_parse_htaccess()(r->htaccess) also belongs to a single request (server/config.clines 2013–2018). That is why edits to.htaccesstake effect without a restart — and why it is better to switch it off withAllowOverride Nonewhen you don't use it.
Easy to get wrong
- A merge function must not modify
base. What comes in asbaseis either a struct created at startup and shared by every request, or the result of another merge. Allocate a new struct and return it, as the example'smerge_dir()does. The core putting its result into a new vector (theapr_pallocinap_merge_per_dir_configs()above) reads the same way. - You need a value that means "not set". The example uses
NULLand-1; the core uses separate bits such asOR_UNSET(server/core.clines 273–275). If you use 0 as the default, you cannot tell "set to Off" from "not set at all": either an innerOfffails to override an outerOn, or an inner section that set nothing wipes out the outer value. - The directory-config merge function is called at startup too — when a virtual host's
lookup_defaultsis combined with the main server's (the last line of the startup trace). If you memorise "directory configs are merged only at request time", that one line has no explanation.
What this post leaves out
To keep to the main thread, the following are only named with their locations. <If>, <ElseIf>, <Else> and how they merge (ifsection(), ap_if_walk() — from server/request.c line 2027). <Files> and ap_file_walk() (server/core.c line 2711, server/request.c line 1690). <Limit> and <LimitExcept> (ap_limit_section()). The PROXY_CONF bit, used only inside <Proxy> (include/http_config.h line 252). AllowOverrideList, which allows directives by name, and the override_list check in invoke_cmd() (server/core.c line 2001, server/config.c lines 862–866). AllowOverride Nonfatal=… (NONFATAL_*), which downgrades .htaccess errors to warnings. Options and override_opts. ap_resolve_env(), which substitutes ${VARIABLE} inside config values (line 1132). Generic functions such as ap_set_string_slot() and ap_set_flag_slot(), which take just the field offset (APR_OFFSETOF) so you don't have to write a function at all (server/config.c lines 1471–1564). process_command_config(), which adds lines before and after the config from the command line's -C and -c (line 1762).
Compared with the 2.4.x branch, the functions in this post (ap_walk_config_sub(), ap_merge_per_dir_configs(), merge_server_configs(), ap_fixup_virtual_hosts(), and the override value at each spot) are the same code. Trunk adds a parent argument to invoke_cmd() and parent and regex fields to cmd_parms (that is why ap_walk_config_sub() passes one extra NULL to invoke_cmd(), and where the two old_regex lines in dirsection() in section 6 come from), plus two lines in ap_parse_htaccess() that mark the request as "affected by .htaccess" (r->taint).
Summary
| Concept | One-line summary |
|---|---|
| Two passes | First read the file into an ap_directive_t tree (ap_build_config), then after pre_config walk the tree and run it (ap_walk_config) |
EXEC_ON_READ |
Directives that change the shape of the file, like LoadModule, Include and <IfModule>, run the moment they are read |
command_rec |
One row of a module's directive table: name, function, where-bits, argument style, help text |
ap_config_hash |
Lower-case name → list of (module, command_rec). Finding is a single hash lookup |
args_how |
13 styles such as TAKE1, FLAG, ITERATE, RAW_ARGS. The core splits the words and checks the count |
cmd_parms |
What a directive function receives about "where we are reading": server, path, allowed bits, node |
| Context check | If cmd->req_override & parms->override is 0: not allowed here / in <X> context |
AllowOverride |
Sets parms->override for walking .htaccess. Not writing it at all is different from None |
| Sections | Core RAW_ARGS directives. They change cmd_parms and walk again from first_child |
| Server config merge | At startup, in ap_fixup_virtual_hosts(). An empty virtual-host slot just gets the pointer copied |
| Directory config merge | On every request, in request.c's walks. <Directory> and .htaccess first, then <Location>. <Location> sections in the order written in the config file |
| Merge rules | The core only calls the module's merge function when both slots are filled; the module decides what wins |
What's next
We have seen a line of the config file go into a module's struct, and the structs created per section be combined into one at request time. Next we move to the side that actually runs the server. Directives like StartServers, MaxRequestWorkers and ThreadsPerChild reach the MPM's variables the same way we saw here — through AP_INIT_TAKE1 rows in the MPM module's directive table (server/mpm/event/event.c lines 4511–4524). How many processes and threads are started with those values, and who gets each incoming connection, continues in Part 8: MPMs — How prefork, worker and event Share Out Connections. Where the walk functions that this post only described as "merging on every request" sit is covered in Part 10.
Compared with Spring, there are two similarities. Spring, too, does not turn configuration straight into objects: it first reads and registers it as blueprints (BeanDefinition) and builds objects later, and it builds a separate merged copy that combines a parent blueprint with a child one (Spring Part 3). httpd's directive tree and merged config vectors correspond to those two stages. The difference is how priority between values is decided. Spring's Environment decides the winning value by the order of a list of sources (PropertySource) and uses the first value found (Spring Part 12), whereas httpd calls the merge function the module wrote every time sections overlap, which lets each module have its own rule (override, append).