PostgreSQLIII · Configuration ArchitectureConfiguration
Where configuration comes from
What you'll learn
- List the files a server reads and the order in which they are applied
- Explain why postgresql.auto.conf overrides postgresql.conf
- Use include, include_dir and include_if_exists deliberately
- Locate the file and line a given setting actually came from
Prerequisites
Verified against PostgreSQL 18.x · PostgreSQL (comparison targets) 17.11, 16.15 · PostgreSQL (support calendar) 18, 17, 16, 15, 14 supported · pgBackRest 2.59.1 · PgBouncer 1.25.2 · Patroni 4.1.5 · Ubuntu (host baseline) 26.04 LTS · 2026-08-27
“I edited postgresql.conf, reloaded, and the value did not change” is
a complaint with about five possible causes, and all of them are
diagnosable in one query. What they have in common is that the file you
edited is not the only file, and it may not be the last one read.
The assembly order
flowchart TD
A["postgresql.conf\nread top to bottom"] --> B["include / include_dir /\ninclude_if_exists\nread where the directive appears"]
B --> C["postgresql.auto.conf\nread LAST, always"]
C --> D["Effective server configuration"]
D --> E["Per-database setting"]
E --> F["Per-role setting"]
F --> G["Per-session SET"]
The rule for the top three boxes is simply later wins. That has three consequences worth stating separately.
A duplicate setting later in a file beats the earlier one. A file
that sets work_mem on line 120 and again on line 640 is running the
line 640 value. This is how a carefully considered value at the top of
a file gets silently replaced by a copy-pasted block at the bottom.
An included file beats the lines above the include directive, and
loses to the lines below it. Position matters, and putting the
include at the very end of postgresql.conf is what most people
actually intend.
postgresql.auto.conf beats everything in the files. It is read
last, unconditionally, and it cannot be reordered.
postgresql.auto.conf
This is the file ALTER SYSTEM writes. It lives in the data directory
regardless of packaging — even on Debian, where everything else moved
to /etc.
$ cat $PGDATA/postgresql.auto.conf# Do not edit this file manually!
# It will be overwritten by the ALTER SYSTEM command.
work_mem = '8MB'
shared_buffers = '256MB'Include directives
postgresql.conf ships with three include forms, all commented out by
default:
$ grep -nE '^#?include' $PGDATA/postgresql.conf879:#include_dir = '...' # include files ending in '.conf' from
881:#include_if_exists = '...' # include file only if it exists
882:#include = '...' # include file| Directive | Behaviour |
|---|---|
include 'file' | Read it. Fail to start if missing |
include_if_exists 'file' | Read it if present; carry on silently if not |
include_dir 'dir' | Read every *.conf in the directory, in C-locale name order |
include_dir is the one worth adopting, because it turns configuration
into something a configuration-management tool can own without
rewriting a file it does not fully control:
# at the very end of postgresql.conf
include_dir = 'conf.d'
conf.d/
├── 10-memory.conf
├── 20-wal.conf
├── 30-logging.conf
└── 90-local-overrides.conf
The numeric prefixes are load-bearing: files are read in name order, so
90-local-overrides.conf beats 10-memory.conf for any key they share.
That is the same later-wins rule, applied to filenames.
Finding where a value came from
pg_settings carries the answer per parameter:
psql -U postgres -c \
"SELECT name, setting, source, sourcefile, sourceline
FROM pg_settings
WHERE name IN ('work_mem','shared_buffers','log_min_duration_statement')"
pg_file_settings answers the complementary question — what do the
files say, including entries that lost:
$ psql -U postgres -c 'SELECT sourcefile, sourceline, name, setting, applied FROM pg_file_settings ORDER BY sourcefile, sourceline LIMIT 8' sourcefile | sourceline | name | setting | applied
-----------------------------------------------+------------+----------------------------+---------+---------
/var/lib/postgresql/18/docker/postgresql.conf | 60 | listen_addresses | * | t
/var/lib/postgresql/18/docker/postgresql.conf | 65 | max_connections | 100 | t
/var/lib/postgresql/18/docker/postgresql.conf | 132 | shared_buffers | 128MB | t
/var/lib/postgresql/18/docker/postgresql.conf | 155 | dynamic_shared_memory_type | posix | t
/var/lib/postgresql/18/docker/postgresql.conf | 271 | max_wal_size | 1GB | t
/var/lib/postgresql/18/docker/postgresql.conf | 272 | min_wal_size | 80MB | t
/var/lib/postgresql/18/docker/postgresql.conf | 645 | log_timezone | Etc/UTC | t
/var/lib/postgresql/18/docker/postgresql.conf | 687 | autovacuum_worker_slots | 16 | t
(8 rows)The applied column is the useful one. A row with applied = false
is a line somebody wrote that is having no effect, and the reason is
almost always that a later line or a later file set the same key.
# Every configuration line that is being ignored, and why it exists
psql -U postgres -c \
"SELECT sourcefile, sourceline, name, setting, error
FROM pg_file_settings WHERE NOT applied OR error IS NOT NULL"
Running that after every configuration change is a two-second check that catches duplicate keys, typos and losing overrides before they become a puzzle.
Production discipline
- Read
postgresql.auto.confbefore investigating any “my edit did nothing” report. It is read last and it wins. - Remove
ALTER SYSTEMentries withALTER SYSTEM RESET, never by editing the file, which the server rewrites wholesale. - Put
include_dirat the end ofpostgresql.confand use numeric filename prefixes, because both the directive position and the file order follow later-wins. - Use
include_if_existsfor anything conditional. A missingincludetarget stops the server from starting, and a reload will not tell you. - Check
pg_file_settingsforapplied = falseand non-nullerrorafter every change. It catches duplicate keys and typos that the reload silently absorbed.
Cross-course references
- Ansible for Production Sysadmins — Part XVII (Templates) covers
managing a
conf.ddirectory as generated fragments, which is the patterninclude_direxists to enable. - Git, CI/CD & GitOps — Part LXXV (Drift) covers detecting when the file on disk stops matching the file in the repository, which is the same gap this lesson’s Under the Hood section describes.
- Linux for Production Sysadmins — Part VII (systemd) covers reading the service log after a reload, which is where a rejected configuration reports itself.
Quiz
Knowledge check · 6 questions
Q1. An operator edits work_mem in postgresql.conf, reloads, and pg_settings still reports the previous value. What should they check first?
Q2. A configuration file contains an include directive pointing at a file that does not exist on this host. The server is currently running. What happens?
Q3. Which of these follow from the later-wins rule? Select all that apply.
Q4. Editing postgresql.auto.conf by hand is a reliable way to remove a setting that ALTER SYSTEM previously wrote.
Q5. Name the view and column that reveal a configuration line which is present in a file but having no effect, and give one common reason a line ends up in that state.
Q6. Explain the failure and give the check that would have caught it.
A configuration-management change added include_if_exists = 'conf.d/50-tuning.conf' to postgresql.conf on forty database hosts and reloaded each one. Monitoring reported all forty healthy. Six weeks later, during routine patching, four hosts failed to start with a configuration error and stayed down for forty minutes while the cause was found. Investigation showed the change had been edited by hand on those four hosts during an unrelated incident, replacing include_if_exists with include, and on those four hosts the referenced file had never been created.
Passing score: 75%. Answers are checked in this browser.