Server Configuration

This note establishes a production-oriented PostgreSQL baseline for shared memory, per-query memory, temp-file spill behavior, WAL checkpoint cadence, and Linux host settings, using the current stoxx-postgres PostgreSQL 16.13 container as the concrete reference point. The goal is the same as in the SQL Server chapter: separate product defaults from operationally safe defaults. The PostgreSQL equivalents are different because PostgreSQL relies on postgresql.conf, pg_hba.conf, pg_settings, and Linux kernel behavior rather than sp_configure, TempDB, and SQLOS memory brokers.

Instance Baseline

Start by checking the small set of instance-level settings that most often separate a safe PostgreSQL build from a default cluster created by a package or Docker image.

High-Impact Instance Settings

This subsection focuses on the settings that most directly affect shared memory, planner expectations, spill behavior, WAL checkpoint cadence, and instrumentation visibility. Before interpreting any query output, read the reference below so that every setting name and every configuration context carries operational meaning rather than just looking like another row in pg_settings.

SettingWhat it controlsDefaultPossible valuesProduction guidance
shared_buffersMain PostgreSQL shared buffer cache.128MB in this PostgreSQL 16 buildInteger blocks in 8kB units or memory units such as MB and GB. Startup-only.On a dedicated host with at least 1 GB of RAM, PostgreSQL documentation recommends a starting point around 25% of system memory. Treat larger than 40% as exceptional and evidence-driven.
effective_cache_sizePlanner estimate of total cache available to a query.4GBInteger blocks or memory units. Session-overridable.Set it to a realistic estimate of shared_buffers plus the PostgreSQL-relevant portion of the OS page cache after accounting for concurrency.
work_memMemory per sort or hash operation before spill.4MB64kB to very large values. Session-overridable.Keep conservative at the instance level. Raise locally for controlled statements rather than globally for every session.
maintenance_work_memMemory for maintenance operations such as VACUUM, CREATE INDEX, and ALTER TABLE.64MB1MB upward. Session-overridable.Raise deliberately on systems that perform sizable vacuum or index-maintenance work; it is far safer to enlarge than work_mem because it is not multiplied across every executor node.
max_connectionsMaximum concurrent backend processes.1001 to 262143, startup-only.Do not raise casually. If concurrency pressure exists, prefer external pooling before turning PostgreSQL into a process farm.
checkpoint_timeoutMaximum time between automatic checkpoints.300s (5min)30s to 86400s, reloadable.5min is conservative. Busy OLTP or mixed workloads often benefit from a longer interval such as 15min when paired with a larger max_wal_size.
max_wal_sizeWAL volume threshold that can trigger checkpoints.1GB2MB upward, reloadable.Raise together with checkpoint_timeout when checkpoints are too frequent. The setting should spread writes, not hide uncontrolled WAL growth.
min_wal_sizeLower bound to which WAL can shrink after checkpoint recycling.80MB2MB upward, reloadable.Raise above the tiny default on systems with steady write volume so PostgreSQL stops oscillating between too little and too much retained WAL.
wal_levelWAL detail required for crash recovery, replication, and logical decoding.replicaminimal, replica, logical, startup-only.Keep at replica or higher on any estate where PITR, standby replicas, or future replication are realistic requirements.
track_io_timingWhether PostgreSQL records I/O timings.offoff or on, superuser-settable.Enable deliberately once overhead has been accepted, because modern troubleshooting is much weaker without I/O timing.
random_page_costPlanner cost estimate for nonsequential page access.4Any positive real number.Lower on SSD-backed or heavily cached systems so index access is not penalized like spinning-disk random I/O.
effective_io_concurrencyExpected number of efficient concurrent storage I/O requests.1 on this Linux container0 to 1000.Raise on SSD or cloud block storage that can service parallel I/O well; 1 is usually too conservative for modern storage.
huge_pagesWhether PostgreSQL requests explicit huge pages.tryoff, on, try, startup-only.try is a safe default. Use on only when huge-page allocation is guaranteed and start failure is an acceptable guardrail.
temp_buffersMaximum temporary-buffer space per session for temporary tables.8MB100 blocks upward, session-overridable before first temp-table use.Keep moderate. This is not the main sort/hash spill control; it applies to temp tables only.
temp_file_limitMaximum disk usage for temp files per backend.-1 (unlimited)-1 or any non-negative size.Set a finite ceiling on production systems so one backend cannot consume unbounded temp space.
log_temp_filesThreshold above which temp-file creation is logged.-1 (disabled)-1, 0, or size in kB.Set to a positive threshold so large spills become visible in logs before disk usage becomes mysterious.

The query in the next subsection reads the current live values of these settings from pg_settings and reports the source and context of each one.

Current configuration values for the settings that matter first

On any new or inherited PostgreSQL instance, immediately after the first successful login as a superuser or privileged operator. It is typically triggered by first configuration audit, post-migration review, post-image-bootstrap verification, or incident response when unexpected planner or checkpoint behavior suggests a misconfiguration. Runs against any database, read-only against pg_settings, and is safest as a superuser because that guarantees visibility of source metadata. Surface the settings that most often separate a deliberate PostgreSQL build from image defaults so the reviewer can decide which values require follow-up through ALTER SYSTEM, configuration management, reload, or restart.

SELECT
    name,
    setting,
    unit,
    context,
    source,
    sourcefile,
    pending_restart
FROM pg_settings
WHERE name IN
(
    'checkpoint_timeout',
    'effective_cache_size',
    'effective_io_concurrency',
    'huge_pages',
    'log_temp_files',
    'maintenance_work_mem',
    'max_connections',
    'max_wal_size',
    'min_wal_size',
    'random_page_cost',
    'shared_buffers',
    'shared_preload_libraries',
    'temp_buffers',
    'temp_file_limit',
    'track_io_timing',
    'wal_level',
    'work_mem'
)
ORDER BY name;
namesettingunitcontextsourcesourcefilepending_restart
checkpoint_timeout300ssighupdefaultf
effective_cache_size5242888kBuserdefaultf
effective_io_concurrency1userdefaultf
huge_pagestrypostmasterdefaultf
log_temp_files-1kBsuperuserdefaultf
maintenance_work_mem65536kBuserdefaultf
max_connections100postmasterconfiguration file/var/lib/postgresql/data/postgresql.conff
max_wal_size1024MBsighupconfiguration file/var/lib/postgresql/data/postgresql.conff
min_wal_size80MBsighupconfiguration file/var/lib/postgresql/data/postgresql.conff
random_page_cost4userdefaultf
shared_buffers163848kBpostmasterconfiguration file/var/lib/postgresql/data/postgresql.conff
shared_preload_librariespostmasterdefaultf
temp_buffers10248kBuserdefaultf
temp_file_limit-1kBsuperuserdefaultf
track_io_timingoffsuperuserdefaultf
wal_levelreplicapostmasterdefaultf
work_mem4096kBuserdefaultf

This is mostly a Docker-image default configuration, not a production baseline. The most important drifts are shared_buffers = 128 MB on a host that exposes about 30.9 GB of memory to the container, effective_cache_size = 4 GB as a generic planner estimate, work_mem = 4 MB and maintenance_work_mem = 64 MB as conservative defaults, effective_io_concurrency = 1 on modern storage, track_io_timing = off, and fully unbounded spill visibility with log_temp_files = -1 and temp_file_limit = -1. wal_level = replica is already a sensible default, and the cluster has no pending restart work at the moment.

ColumnValueWatchMeaningImplication
contextpostmasterRestart boundarySetting only changes at postmaster startALTER SYSTEM or file edits alone are not enough; restart planning is required
contextsighupReload boundarySetting changes after configuration reloadpg_reload_conf() or a SIGHUP is the right activation path
contextsuperuserControlled session overrideSuperusers can change it in-sessionUseful for diagnostics, but estate-wide defaults still belong in configuration
contextuserSession-local override allowedAny session can change it locallyGlobal defaults should stay conservative because application code can raise them for one session
sourcedefaultWatchPostgreSQL is still using packaged defaultsGood for labs, weak for production hardening
sourceconfiguration fileContextValue is explicitly set in postgresql.conf or an included fileBaseline is deliberate rather than inherited
pending_restarttWatchMetadata and running state divergeA restart-only setting has been changed and is waiting to become active
pending_restartfRunning state matches accepted configurationNo latent restart work for the audited settings

CPU And Memory Context

Configuration values are not meaningful without the host context they run in. A shared_buffers value of 8GB is sensible on one host and reckless on another. A work_mem value of 16MB can be conservative on a lightly pooled application and dangerous on a server that allows hundreds of active backends to build several hash tables at once. This subsection shows the CPU count and memory visible to the PostgreSQL container so the rest of the note has operational scale attached to it.

FieldSource commandUnitMeaning
CPU countnprocintegerNumber of CPUs visible to the PostgreSQL container. PostgreSQL itself does not expose an equivalent one-row catalog view for this, so the host shell is the right source of truth.
total memoryfree -mMiBTotal memory visible inside the container’s Linux environment. In this Dockerized lab, that corresponds to the memory envelope seen by PostgreSQL.
used memoryfree -mMiBCurrently used memory on the Linux host environment, inclusive of non-PostgreSQL processes and cache.
buff/cache memoryfree -mMiBMemory currently serving as buffer cache and reclaimable page cache. This is part of the reason effective_cache_size must consider both PostgreSQL buffers and the OS cache.
available memoryfree -mMiBKernel estimate of memory available for new allocations without heavy reclaim or swap pressure.

CPU count and host memory visible to the PostgreSQL container

Any time a memory-related or concurrency-related decision is on the table, especially sizing shared_buffers, setting effective_cache_size, reviewing work_mem, or deciding whether the current default max_connections is tolerable. It is typically triggered by first baseline audit, capacity planning, post-host-resize verification, or incident response after OOM or spill-heavy behavior. Runs in the Linux shell of the PostgreSQL host or container runtime, not in SQL. Read-only. Provide the CPU and memory envelope visible to PostgreSQL so configuration values in this note have real operational scale.

nproc
free -m
16
               total        used        free      shared  buff/cache   available
Mem:           30914        6094        3012         194       22362       24819
Swap:           8192          15        8176

The PostgreSQL runtime currently sees 16 CPUs and about 30.9 GB of memory, with about 24.8 GB reported as available. That context makes the default shared_buffers = 128 MB obviously conservative and makes the default effective_cache_size = 4 GB look like a generic estimate rather than a host-specific planner value. The current database size is only about 44 MB, so the lab is not memory-constrained today, but the configuration still reflects packaged defaults rather than an intentionally tuned production baseline.

ColumnValueWatchMeaningImplication
CPU count16ContextHost exposes 16 processing units to PostgreSQLPlenty of concurrency headroom; connection count and I/O behavior matter more than CPU scarcity in this lab
available memory24819 MBContextRoughly 24.2 GB is currently availableRaising shared_buffers materially above 128 MB is feasible without immediate host pressure
buff/cache22362 MBContextLinux is already using a large page-cache footprintPlanner cache estimates should not be based on PostgreSQL shared memory alone
SwapMostly unusedHost is not currently paging heavilyGood current state, but default policy can still be wrong for production

The current baseline calls for a small number of concrete changes before this instance would qualify as production-hardened.

Set planner, spill, and I/O timing defaults deliberately

After the audit query above has confirmed that effective_cache_size, work_mem, maintenance_work_mem, random_page_cost, effective_io_concurrency, track_io_timing, log_temp_files, and temp_file_limit are still at packaged defaults. It is typically triggered by initial production hardening of a new PostgreSQL build, post-migration cleanup, or remediation after planner misestimation, hidden temp spills, or weak I/O diagnostics. Runs as a superuser or a role allowed to change these settings with ALTER SYSTEM. These settings are either reloadable or session-level, so pg_reload_conf() is sufficient after the file update. Apply the lowest-risk, highest-value baseline improvements first: a realistic planner cache estimate, safer spill observability, more realistic storage-cost assumptions, and I/O timing instrumentation.

work_mem multiplies faster than people expect

Do not treat work_mem like a per-connection memory reservation. A single complex query can consume several work_mem allocations at once. Keep the instance default conservative and use session-local overrides for exceptional statements.

Make spills visible before raising memory

Enable track_io_timing and log_temp_files, set a finite temp_file_limit, and only then consider raising work_mem. That sequence makes bad spill behavior measurable instead of speculative.

ALTER SYSTEM SET effective_cache_size = '24GB';
ALTER SYSTEM SET work_mem = '8MB';
ALTER SYSTEM SET maintenance_work_mem = '512MB';
ALTER SYSTEM SET random_page_cost = '1.1';
ALTER SYSTEM SET effective_io_concurrency = '32';
ALTER SYSTEM SET track_io_timing = 'on';
ALTER SYSTEM SET log_temp_files = '65536';
ALTER SYSTEM SET temp_file_limit = '4GB';
 
SELECT pg_reload_conf();

Live state of these settings on stoxx-postgres

The audit query at the top of this section already captures the live values on stoxx-postgreseffective_cache_size = 4GB, work_mem = 4MB, maintenance_work_mem = 64MB, effective_io_concurrency = 1, random_page_cost = 4, track_io_timing = off, and both temp-file controls disabled or unlimited. The batch above is shown as the intended remediation pattern and has not been executed against the lab, so the note preserves a clean “before” baseline.

Set shared memory and preload settings deliberately

After the audit query has confirmed that startup-only settings such as shared_buffers, huge_pages, and shared_preload_libraries are still at image defaults or unset. It is typically triggered by initial production hardening, deliberate observability enablement, or a host resize that changes what a sensible shared-memory budget looks like. Runs as a superuser. These settings are postmaster context settings, so ALTER SYSTEM only writes the values; a full PostgreSQL restart is still required before they become effective. Replace tiny image defaults with deliberate shared-memory settings and preload only the extensions that are actually part of the operational model.

Restart-only settings need a real maintenance window

shared_buffers, huge_pages, and shared_preload_libraries do not take effect on reload. Treat them like any other restart-scoped production change.

Validate with pg_settings before and after restart

Write the settings, reload to confirm syntax, restart the postmaster in a controlled window, and then re-run the baseline audit to prove the running values changed and pending_restart returned to false.

ALTER SYSTEM SET shared_buffers = '8GB';
ALTER SYSTEM SET huge_pages = 'try';
ALTER SYSTEM SET shared_preload_libraries = 'pg_stat_statements';
 
SELECT pg_reload_conf();

Live state of these settings on stoxx-postgres

The live cluster currently shows shared_buffers = 128MB, huge_pages = try, and an empty shared_preload_libraries value. The example above is a production-style pattern only; it has not been executed against the lab. If it were, shared_buffers and shared_preload_libraries would remain pending until a restart, which the audit query would reflect through pending_restart = true.


Temporary Workloads And WAL

PostgreSQL has no TempDB equivalent. The nearest operational surface is the combination of per-operation work memory, per-session temp buffers, temporary-file spill limits, and the WAL/checkpoint settings that determine how aggressively dirty work is forced to disk.

Spill And Checkpoint Controls

This subsection verifies the settings that govern temp spills and checkpoint cadence. Before reading the query, the settings it returns must be understood, because PostgreSQL temp behavior is controlled through configuration rather than by inspecting a separate temp database file layout.

FieldSource settingUnitMeaning
work_mempg_settings.work_memkB or memory unitsBase memory available to a sort, hash, materialize, or similar executor node before it spills to temp files.
temp_bufferspg_settings.temp_buffersblocks or memory unitsPer-session buffer budget for temporary tables only. This is distinct from sort/hash temp spills.
log_temp_filespg_settings.log_temp_fileskBThreshold above which PostgreSQL logs temp-file creation. -1 disables the logging completely.
temp_file_limitpg_settings.temp_file_limitkBMaximum temp-file space allowed per backend process. -1 means no limit.
checkpoint_timeoutpg_settings.checkpoint_timeoutsecondsTime-based upper bound on how long PostgreSQL waits between automatic checkpoints.
max_wal_sizepg_settings.max_wal_sizeMBVolume-based checkpoint trigger. PostgreSQL checkpoints early if WAL growth is about to exceed this size.
min_wal_sizepg_settings.min_wal_sizeMBLower bound for WAL recycling after checkpoint.
wal_levelpg_settings.wal_levelenumThe feature boundary for replication and recovery capability.

Current temp-file and WAL control surface

During initial production hardening, immediately after a spill-related incident, or whenever checkpoint behavior feels more aggressive than expected. It is typically triggered by unexplained disk growth in base/pgsql_tmp, slow sorts and hashes, checkpoint spikes in logs, or first-pass verification after a cluster build. Runs in any database, read-only against pg_settings. Confirm whether PostgreSQL is still running with unbounded temp-file behavior and conservative checkpoint defaults, which is the standard packaged posture but not necessarily a production baseline.

SELECT
    name,
    setting,
    unit,
    context,
    source
FROM pg_settings
WHERE name IN
(
    'checkpoint_timeout',
    'log_temp_files',
    'max_wal_size',
    'min_wal_size',
    'temp_buffers',
    'temp_file_limit',
    'wal_level',
    'work_mem'
)
ORDER BY name;
namesettingunitcontextsource
checkpoint_timeout300ssighupdefault
log_temp_files-1kBsuperuserdefault
max_wal_size1024MBsighupconfiguration file
min_wal_size80MBsighupconfiguration file
temp_buffers10248kBuserdefault
temp_file_limit-1kBsuperuserdefault
wal_levelreplicapostmasterdefault
work_mem4096kBuserdefault

This control surface is still close to a generic package baseline. work_mem = 4MB and temp_buffers = 8MB are conservative defaults, which is safe, but PostgreSQL is currently blind to large temp spills because log_temp_files = -1 and it does not cap per-backend spill growth because temp_file_limit = -1. The checkpoint settings are also at conservative defaults: checkpoint_timeout = 5min, max_wal_size = 1GB, and min_wal_size = 80MB. Those are acceptable for a tiny 44 MB lab database, but they are not a deliberate production posture for a write-heavy system.

ColumnValueWatchMeaningImplication
log_temp_files-1Temp-file logging disabledLarge spills can happen with no log evidence
temp_file_limit-1No per-backend spill capOne backend can consume arbitrary temp space
checkpoint_timeout300sWatchConservative checkpoint cadenceFine for tiny systems, often too eager for busier ones
max_wal_size1GBWatchSmall WAL budget before checkpoint pressure increasesCan force frequent checkpoints on sustained-write workloads
wal_levelreplicaRecovery and physical replication capability preservedGood default stance for most estates

Set a spill-safe and checkpoint-friendly baseline

After the audit query has confirmed that temp-file logging is disabled, temp-file growth is unbounded, and checkpoint cadence is still near the packaged defaults. It is typically triggered by first hardening pass, temp-disk incident review, or sustained-write workloads that are checkpointing too often. Runs as a superuser. All settings in this batch are reloadable, so pg_reload_conf() is sufficient after the change. Make PostgreSQL temp spills visible and bounded, and spread checkpoints more deliberately across time and WAL volume.

Bigger WAL windows are not a free performance button

Raising checkpoint_timeout and max_wal_size can reduce checkpoint pressure, but it also lengthens crash-recovery distance and increases the amount of WAL that must be retained or archived.

Increase observability before increasing memory

Enable temp-file logging and set a finite temp_file_limit before reacting to slow sorts by raising work_mem. That sequence tells you whether the issue is truly spill-driven and how large the spills actually are.

ALTER SYSTEM SET log_temp_files = '65536';
ALTER SYSTEM SET temp_file_limit = '4GB';
ALTER SYSTEM SET checkpoint_timeout = '15min';
ALTER SYSTEM SET max_wal_size = '4GB';
ALTER SYSTEM SET min_wal_size = '512MB';
 
SELECT pg_reload_conf();

Live state of temp and WAL controls on stoxx-postgres

The audit query above already captures the live state of the lab — logging disabled, spill unlimited, and checkpoints still near the packaged defaults. The batch above is the intended production pattern and has not been executed against stoxx-postgres, so the lab remains a clean default baseline for teaching.


Linux Host Settings

The PostgreSQL instance in this environment runs on Linux inside Docker, so a few host-level checks still matter even though they are outside SQL.

Host Checks

These commands must be run on the Linux host or container runtime that runs PostgreSQL. They read pseudo-files under /proc and /sys that expose live kernel state. For PostgreSQL, the most important Linux-memory checks are the overcommit policy, swap aggressiveness, and current Transparent Huge Pages mode.

FieldSource pathPossible valuesMeaning for PostgreSQL
Overcommit policy/proc/sys/vm/overcommit_memory0, 1, or 2PostgreSQL documentation explicitly warns that Linux memory overcommit can let the OOM killer terminate the postmaster. 2 is the strict mode recommended for more robust PostgreSQL behavior on dedicated hosts.
Swap aggressiveness/proc/sys/vm/swappinessInteger kernel policy valueControls how aggressively the kernel prefers swapping anonymous memory. A value like 60 is a general-purpose default, not a database-specific one.
Transparent Huge Pages/sys/kernel/mm/transparent_hugepage/enabled[always], [madvise], [never]Shows the kernel’s THP mode. This is separate from PostgreSQL’s own huge_pages setting and needs to be treated as a host policy choice, not a PostgreSQL GUC.

Verify Linux memory policy on the PostgreSQL host

During initial host validation of a new PostgreSQL deployment, after a kernel upgrade, or during OOM and latency investigations when PostgreSQL configuration alone does not explain the behavior. It is typically triggered by first build validation, unexplained postmaster exits, memory-pressure incidents, or compliance checks against a database-host standard. Runs on the Linux shell, read-only, no SQL involved. Record the current Linux memory-policy values that most directly affect PostgreSQL reliability so the operator can decide whether the host matches the intended baseline before changing anything.

Host-level commands, not PostgreSQL SQL

These commands inspect the Linux kernel environment. They do not run inside PostgreSQL itself and they should not be confused with SHOW or SELECT current_setting(...).

Distinguish PostgreSQL GUCs from kernel policy

Treat huge_pages, work_mem, and checkpoint_timeout as PostgreSQL settings, and vm.overcommit_memory, vm.swappiness, and THP mode as Linux-host policy. Good operations depend on knowing which layer owns the behavior.

cat /proc/sys/vm/overcommit_memory
cat /proc/sys/vm/swappiness
cat /sys/kernel/mm/transparent_hugepage/enabled
1
60
always [madvise] never

Output captured from inside the stoxx-postgres Docker container, which shares the host kernel. The active value in the THP file is shown in square brackets.

ReadingLive value on stoxx-postgres hostRecommended directionAction
vm.overcommit_memory1Move to 2 on a dedicated PostgreSQL hostDrift from the PostgreSQL-doc-recommended strict overcommit posture
vm.swappiness60Lower from general-purpose default when the host is database-dedicatedContext drift — acceptable for a generic Linux build, not an explicit database baseline
THP mode[madvise]Keep explicit and deliberateBetter than [always], but still a host-policy choice that should not be accidental

Configure the Linux host baseline when needed

Only after the verification commands above have shown that the host does not match the platform standard, and only after the change has been validated against the configuration-management path for the fleet. It is typically triggered by baseline drift, new-host preparation before PostgreSQL is put under sustained load, or remediation after a documented OOM or latency incident. Runs on the Linux host shell as a privileged user. The sysctl changes are live-applied to the running kernel; writing to the THP pseudo-file is also live but is not persistent unless configuration management enforces it. Apply the dedicated-host Linux baseline deliberately rather than inheriting a general-purpose kernel profile.

These are host-wide changes

These commands affect the Linux host globally, not only PostgreSQL. Apply them through the host’s normal configuration-management path so they remain persistent and auditable.

Persist the baseline, not just the live value

Use the shell commands to confirm the intended live effect, then persist them through /etc/sysctl.d/, systemd, cloud-init, or the estate’s normal configuration-management tooling.

sudo sysctl vm.overcommit_memory=2
sudo sysctl vm.swappiness=1
echo never | sudo tee /sys/kernel/mm/transparent_hugepage/enabled