Install PostgreSQL with Sensible Tuning
Install PostgreSQL from PGDG repos and apply evidence-based tuning for shared_buffers, work_mem, effective_cache_size, and WAL settings on any Linux server.
Before you start
- ▸A Linux server with at least 2 GB RAM (tuning values scale with available memory)
- ▸sudo or root access
- ▸Basic familiarity with a terminal text editor (nano, vim) for editing postgresql.conf
A default PostgreSQL installation is intentionally conservative — tuned to run on a 256 MB virtual machine from 2005. On any real server you will leave significant performance on the table unless you adjust a handful of key parameters. This guide installs the latest PostgreSQL release from the official PGDG repositories, then applies evidence-based tuning for shared_buffers, work_mem, effective_cache_size, and WAL behaviour.
Install PostgreSQL from PGDG Repositories
Distro-packaged PostgreSQL is often one or two major versions behind. Use the official PostgreSQL Global Development Group (PGDG) repos to get the current release.
Debian / Ubuntu
sudo apt install -y curl ca-certificates gnupg
curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc \
| sudo gpg --dearmor -o /usr/share/keyrings/pgdg.gpg
echo "deb [signed-by=/usr/share/keyrings/pgdg.gpg] \
https://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" \
| sudo tee /etc/apt/sources.list.d/pgdg.list
sudo apt update
sudo apt install -y postgresql-16
Fedora / RHEL 9 / Rocky Linux 9
sudo dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-9-x86_64/pgdg-redhat-repo-latest.noarch.rpm
sudo dnf -qy module disable postgresql
sudo dnf install -y postgresql16-server postgresql16-contrib
sudo /usr/pgsql-16/bin/postgresql-16-setup initdb
sudo systemctl enable --now postgresql-16
Arch Linux
sudo pacman -S postgresql
sudo -u postgres initdb --locale=C.UTF-8 -D /var/lib/postgres/data
sudo systemctl enable --now postgresql
On Debian/Ubuntu the package ships with a default cluster already initialised and the service already enabled. Verify it is running before continuing:
sudo systemctl status postgresql
Locate postgresql.conf
All tuning lives in postgresql.conf. Its path varies by distro and major version.
- Debian/Ubuntu:
/etc/postgresql/16/main/postgresql.conf - RHEL/Fedora/Rocky:
/var/lib/pgsql/16/data/postgresql.conf - Arch:
/var/lib/postgres/data/postgresql.conf
If you are unsure, ask PostgreSQL itself:
sudo -u postgres psql -c 'SHOW config_file;'
Many production setups drop overrides into conf.d/ rather than editing the main file directly. That approach is cleaner for config management. Check whether your install includes a conf.d include:
grep 'include_dir' /etc/postgresql/16/main/postgresql.conf
If it does, create a dedicated tuning file:
sudo touch /etc/postgresql/16/main/conf.d/tuning.conf
sudo chown postgres:postgres /etc/postgresql/16/main/conf.d/tuning.conf
Memory Tuning
shared_buffers
shared_buffers is PostgreSQL's dedicated buffer cache — shared across all connections. The standard starting point is 25 % of total RAM. On a system with 16 GB RAM, set it to 4 GB. PostgreSQL also relies on the OS page cache, so setting this above 40 % typically shows diminishing returns.
# Determine your total RAM in MB
awk '/MemTotal/ {printf "%d MB\n", $2/1024}' /proc/meminfo
Then set the value in postgresql.conf (or tuning.conf):
shared_buffers = 4GB # 25 % of 16 GB RAM example
On Linux, shared_buffers values above 2 GB require that huge_pages be considered. If your kernel's shmmax is too low, PostgreSQL will refuse to start. Check and raise it if needed:
cat /proc/sys/kernel/shmmax
# If lower than your shared_buffers value in bytes, raise it persistently:
echo 'kernel.shmmax = 8589934592' | sudo tee -a /etc/sysctl.d/90-postgres.conf
sudo sysctl -p /etc/sysctl.d/90-postgres.conf
work_mem
work_mem is the memory each sort or hash operation can use before spilling to disk. The danger is that a single query can open many sort nodes simultaneously, and each connection can run multiple queries. On a busy server with 100 connections each running a complex query, even a modest work_mem multiplies quickly.
A pragmatic formula: (Total RAM - shared_buffers) / (max_connections × 2). For 16 GB RAM, 4 GB shared_buffers, and 100 max_connections that gives roughly 60 MB — a reasonable default for an OLTP workload.
work_mem = 64MB # adjust based on formula above
max_connections = 100 # set this deliberately; do not leave it unbounded
For analytics-heavy workloads with fewer long-running queries, raising work_mem to 256 MB or higher is worthwhile. For OLTP with many short connections, prefer a connection pooler (PgBouncer) and keep work_mem lower.
effective_cache_size
effective_cache_size does not allocate memory. It is a planner hint: how much total memory (PostgreSQL buffers + OS page cache) can the planner assume is available for caching data? A higher value encourages the planner to favour index scans over sequential scans. Set it to roughly 75 % of total RAM.
effective_cache_size = 12GB # 75 % of 16 GB RAM example
WAL and Checkpoint Tuning
Write-Ahead Log settings have a large impact on both write throughput and crash recovery time.
wal_buffers
The default of -1 (auto: 1/32 of shared_buffers, capped at 16 MB) is usually fine. For write-heavy workloads, 64 MB is a safe ceiling:
wal_buffers = 16MB # or 64MB for heavy write workloads
checkpoint_completion_target
Checkpoints flush dirty pages to disk. Spreading that I/O over a longer fraction of the checkpoint interval reduces latency spikes. The default is 0.9, which is already sensible in PostgreSQL 14+; leave it unless you have reason to change it.
checkpoint_completion_target = 0.9
min_wal_size and max_wal_size
max_wal_size controls how large the WAL can grow between checkpoints (default: 1 GB). Raising it reduces checkpoint frequency at the cost of longer recovery time after a crash. For a write-heavy workload with fast storage, 4 GB is reasonable:
min_wal_size = 1GB
max_wal_size = 4GB
synchronous_commit
By default PostgreSQL waits for WAL to be flushed to disk before confirming a transaction. Setting synchronous_commit = off allows up to wal_writer_delay ms (default 200 ms) of data loss in a crash, but can double write throughput. This is acceptable for session-level analytics or queues, but think carefully before applying it cluster-wide.
# Cluster-wide: only if data loss of ~200ms on crash is acceptable
synchronous_commit = off
# Or per-session / per-transaction for specific use cases:
# SET LOCAL synchronous_commit = off;
Apply Changes and Verify
Most memory and WAL parameters require a full service restart. A reload is not sufficient for shared_buffers.
# Debian/Ubuntu
sudo systemctl restart postgresql@16-main
# RHEL/Fedora/Rocky
sudo systemctl restart postgresql-16
# Arch
sudo systemctl restart postgresql
Verify your settings took effect inside psql:
sudo -u postgres psql -c "
SELECT name, setting, unit
FROM pg_settings
WHERE name IN (
'shared_buffers','work_mem','effective_cache_size',
'wal_buffers','max_wal_size','synchronous_commit'
);
"
You should see your new values reflected in the setting column. shared_buffers and wal_buffers are reported in 8 kB blocks; divide by 128 to convert to MB.
Troubleshooting
- PostgreSQL fails to start after raising shared_buffers: The most common cause is
shmmaxbeing too low (see the kernel section above) orhuge_pages = tryfailing silently. Checkjournalctl -u postgresqland/var/log/postgresql/for the exact error. - Out-of-memory kills after raising work_mem: You have set
work_memtoo high relative to the number of concurrent connections and query complexity. Reduce it by half and monitor withpg_stat_activity. - Settings appear unchanged after restart: You may be editing the wrong
postgresql.conf. RunSHOW config_file;insidepsqlto confirm the path, and ensure noconf.dfile is overriding your changes. - Frequent checkpoints despite raising max_wal_size: Check
pg_stat_bgwriter— ifcheckpoints_reqis high, writes are triggering checkpoints before the timeout. Consider raisingmax_wal_sizefurther or investigating write amplification.
Frequently asked questions
- Can I apply these settings without a full service restart?
- No, not for shared_buffers or wal_buffers — those require a restart. Parameters like work_mem and effective_cache_size can be changed with a reload (pg_ctl reload or SIGHUP), but shared memory changes always need a full restart.
- Is the PGTune website a reliable alternative to manual calculation?
- PGTune (pgtune.leopard.in.ua) produces reasonable starting values and is a useful sanity check. Treat its output as a baseline, not a final answer — it cannot know your query patterns, number of databases, or storage characteristics.
- Should I use huge pages with PostgreSQL?
- On systems with shared_buffers above 1 GB, enabling transparent huge pages or setting huge_pages = try in postgresql.conf can reduce TLB pressure and improve throughput. Test with your workload; the benefit varies by hardware and kernel version.
- Why does setting synchronous_commit = off scare DBAs?
- With synchronous_commit off, up to wal_writer_delay milliseconds (default 200 ms) of committed transactions can be lost if the server crashes. The data is not corrupted — the database stays consistent — but recent commits disappear. It is never appropriate for financial or audit data.
- How do I check if my tuning is actually helping?
- Query pg_stat_bgwriter for checkpoint frequency, pg_stat_database for cache hit ratios (aim for >99% on blks_hit vs blks_read), and use EXPLAIN (ANALYZE, BUFFERS) on slow queries to see whether sorts are spilling to disk.
Related guides
Configure Prometheus Alertmanager
Configure Prometheus Alertmanager with routing trees, receivers, inhibition rules, grouping, Go templates, and PagerDuty/Slack on-call integrations.
Build an Intranet Server on Linux
Set up a complete small-office intranet on one Linux box: Nginx web server, dnsmasq local DNS, Samba file sharing, and a Wiki.js team wiki.
Build an nftables Firewall Script
Build a complete nftables firewall from scratch: tables, chains, sets, default-deny input policy, service allowlisting, and persistent systemd configuration.
Caddy as a Reverse Proxy
Set up Caddy as a reverse proxy with automatic HTTPS, load balancing, WebSocket passthrough, reusable snippets, and header control — no certbot required.