{"id":1114,"date":"2026-09-12T22:39:53","date_gmt":"2026-09-12T22:39:53","guid":{"rendered":"https:\/\/virtualserversvps.com\/blog\/?p=1114"},"modified":"2026-09-12T22:39:53","modified_gmt":"2026-09-12T22:39:53","slug":"tuning-postgresql-small-vps-shared-buffers-wal-max-connections","status":"publish","type":"post","link":"https:\/\/virtualserversvps.com\/blog\/tuning-postgresql-small-vps-shared-buffers-wal-max-connections\/","title":{"rendered":"Tuning PostgreSQL for a Small VPS: shared_buffers, WAL, and max_connections"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">PostgreSQL ships with configuration defaults tuned for a developer laptop, not a 2 GB VPS. On a small instance the three settings that cause the most pain are <code>shared_buffers<\/code>, <code>wal_buffers<\/code>\/checkpoint behavior, and <code>max_connections<\/code>. Get them wrong and you either starve the OS page cache, stall on checkpoints, or run out of memory the moment traffic arrives. This guide walks through each with concrete numbers for a 2 vCPU \/ 2 GB and a 4 vCPU \/ 4 GB box.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Know Your Budget Before Touching a Setting<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">On a VPS, RAM is the scarce resource. Split it deliberately: roughly 25% to <code>shared_buffers<\/code>, and let the kernel use the rest as page cache for the data files. Over-allocating <code>shared_buffers<\/code> is the single most common small-VPS mistake \u2014 it looks &#8220;generous&#8221; while actually shrinking the page cache that caches your hot data.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Instance<\/th><th>shared_buffers<\/th><th>effective_cache_size<\/th><th>work_mem<\/th><th>max_connections<\/th><\/tr><\/thead><tbody><tr><td>1 GB RAM<\/td><td>256MB<\/td><td>512MB<\/td><td>4MB<\/td><td>20<\/td><\/tr><tr><td>2 GB RAM<\/td><td>512MB<\/td><td>1GB<\/td><td>8MB<\/td><td>30<\/td><\/tr><tr><td>4 GB RAM<\/td><td>1GB<\/td><td>2GB<\/td><td>12MB<\/td><td>50<\/td><\/tr><tr><td>8 GB RAM<\/td><td>2GB<\/td><td>5GB<\/td><td>16MB<\/td><td>80<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">1. shared_buffers: A Quarter of RAM, Not More<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><code>shared_buffers<\/code> is PostgreSQL&#8217;s own cache of data pages. On Linux the OS page cache already caches the same files, so a huge <code>shared_buffers<\/code> duplicates work and steals memory from that cache. Set it to ~25% of RAM and stop.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># postgresql.conf \u2014 2 GB VPS\nshared_buffers = 512MB\neffective_cache_size = 1GB     # tells the planner how much RAM the OS + PG can cache\n\n# Verify actual page cache usage\nfree -h\ngrep -E 'Buffers|Cached' \/proc\/meminfo<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><code>effective_cache_size<\/code> allocates nothing \u2014 it is a planner hint. Setting it to roughly (shared_buffers + expected page cache) makes the planner prefer index scans when it knows the working set fits in memory.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">2. max_connections: The Silent Memory Bomb<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Each backend process can allocate up to <code>work_mem<\/code> <em>per sort or hash operation<\/em>, and a single query may use several. With <code>max_connections = 200<\/code> and <code>work_mem = 64MB<\/code> (the defaults on some builds), theoretical worst case exceeds 12 GB on a 2 GB box. The correct pattern on a VPS is a <strong>low connection count plus a pooler<\/strong>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Keep real PostgreSQL connections small; let PgBouncer multiplex clients\nmax_connections = 30\nwork_mem = 8MB            # per sort\/hash node\nmaintenance_work_mem = 64MB\n\n# Then run PgBouncer in front:\n# \/etc\/pgbouncer\/pgbouncer.ini\n[databases]\napp = host=127.0.0.1 port=5432 dbname=app\n\n[pgbouncer]\nlisten_port = 6432\npool_mode = transaction\nmax_client_conn = 400\ndefault_pool_size = 20<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Transaction pooling lets 400 PHP or Node clients share 20 real backends. This cuts memory pressure and connection setup cost in one move. Watch <code>pg_stat_activity<\/code> for idle-in-transaction sessions that defeat pooling:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>SELECT state, count(*) FROM pg_stat_activity GROUP BY state;\n-- 'idle in transaction' with a long age is a bug in your application<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">3. WAL and Checkpoints: Stop the Periodic Stalls<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Default checkpoints fire every 5 minutes and force every dirty page to disk at once. On a small VPS with limited IOPS, that produces a latency spike every 5 minutes. Spread the work out by lengthening the interval and sizing the WAL window.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># WAL settings for a busy 2-4 GB VPS\nwal_buffers = 16MB\nmin_wal_size = 256MB\nmax_wal_size = 1GB          # larger = fewer checkpoints, more WAL disk used\ncheckpoint_timeout = 15min\ncheckpoint_completion_target = 0.9   # spread writes across 90% of the interval\nsynchronous_commit = on              # keep on for durability; off only for bulk loads\nwal_compression = on                 # saves WAL disk at a small CPU cost<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">On a 4 vCPU box, let the checkpointer parallelize: <code>checkpoint_flush_after = 256kB<\/code> and <code>backend_flush_after = 256kB<\/code> reduce the size of individual write bursts. If your provider caps disk IOPS, these are the settings that keep you under the cap.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">4. Planner Costs for SSD-Backed VPS Storage<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The default planner costs assume spinning disks with expensive random reads. On NVMe they over-penalize index scans. Lowering them makes the planner pick better plans for the storage you actually have:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>random_page_cost = 1.1      # default 4.0 is for HDDs\neffective_io_concurrency = 200\ndefault_statistics_target = 100<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Always run <code>ANALYZE<\/code> after any change in table size, or let autovacuum handle it \u2014 bad statistics negate every planner tweak. This is the same disk-aware reasoning behind our <a href=\"https:\/\/virtualserversvps.com\/blog\/\">VPS performance tuning guides<\/a> for web stacks.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">A Complete postgresql.conf Block for a 2 GB \/ 2 vCPU VPS<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>max_connections = 30\nshared_buffers = 512MB\neffective_cache_size = 1GB\nwork_mem = 8MB\nmaintenance_work_mem = 64MB\nwal_buffers = 16MB\nmin_wal_size = 256MB\nmax_wal_size = 1GB\ncheckpoint_timeout = 15min\ncheckpoint_completion_target = 0.9\nrandom_page_cost = 1.1\neffective_io_concurrency = 200\nlog_min_duration_statement = 200ms   # log slow queries\nshared_preload_libraries = 'pg_stat_statements'<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">5. Autovacuum: Tune It, Never Disable It<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Faced with an overloaded VPS, admins sometimes disable autovacuum to free up I\/O. That trade buys a few quiet days and then hands you table bloat, transaction-ID wraparound risk, and steadily worse plans as statistics go stale. On a small instance, make autovacuum gentle instead of turning it off.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>autovacuum_max_workers = 2\nautovacuum_naptime = 30s\nautovacuum_vacuum_cost_delay = 20ms   # slower, less disruptive\nautovacuum_vacuum_scale_factor = 0.1  # vacuum at 10% dead rows\n\n-- check what is being vacuumed\nSELECT relname, n_dead_tup, last_autovacuum\nFROM pg_stat_user_tables ORDER BY n_dead_tup DESC LIMIT 10;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Keep the cost delay high so an autovacuum run never competes with foreground queries for your capped disk IOPS \u2014 it simply takes longer, which is exactly the trade you want on a shared plan.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Verifying the Results<\/h2>\n\n\n\n<ul class=\"wp-block-list\"><li><strong>Buffer hit ratio:<\/strong> <code>SELECT sum(blks_hit)*100.0\/sum(blks_hit+blks_read) FROM pg_stat_database;<\/code> \u2014 target 99%+.<\/li><li><strong>Checkpoint spikes:<\/strong> <code>SELECT * FROM pg_stat_bgwriter;<\/code> \u2014 watch <code>checkpoints_timed<\/code> vs <code>checkpoints_req<\/code>; too many requested checkpoints means <code>max_wal_size<\/code> is too small.<\/li><li><strong>Query plans:<\/strong> <code>EXPLAIN (ANALYZE, BUFFERS)<\/code> confirms the planner used the indexes you expected.<\/li><li><strong>Top queries:<\/strong> <code>SELECT query, mean_exec_time FROM pg_stat_statements ORDER BY mean_exec_time DESC LIMIT 10;<\/code><\/li><\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">A tuned PostgreSQL on a modest instance frequently beats a default-configured one on double the hardware. Apply the block above, watch the four metrics for a week, and adjust <code>work_mem<\/code> upward only if you see sorts spilling to disk (<code>log_temp_files<\/code>). If the database still saturates the box, it may be time to give it its own instance \u2014 compare options on the <a href=\"https:\/\/virtualserversvps.com\/\">VPS plans page<\/a> and size for baseline, not peak.<\/p>\n\n","protected":false},"excerpt":{"rendered":"<p>PostgreSQL ships with configuration defaults tuned for a developer laptop, not a 2 GB VPS. On a small instance the three settings that cause the most pain are shared_buffers, wal_buffers\/checkpoint&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"iawp_total_views":1,"footnotes":""},"categories":[3],"tags":[],"class_list":["post-1114","post","type-post","status-publish","format-standard","hentry","category-performance-optimization"],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v26.1 (Yoast SEO v26.1) - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Tuning PostgreSQL for a Small VPS: shared_buffers, WAL, and max_connections - Virtual Servers VPS Blog<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/virtualserversvps.com\/blog\/tuning-postgresql-small-vps-shared-buffers-wal-max-connections\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Tuning PostgreSQL for a Small VPS: shared_buffers, WAL, and max_connections\" \/>\n<meta property=\"og:description\" content=\"Tuning PostgreSQL for a Small VPS: shared_buffers, WAL, and max_connections\" \/>\n<meta property=\"og:url\" content=\"https:\/\/virtualserversvps.com\/blog\/tuning-postgresql-small-vps-shared-buffers-wal-max-connections\/\" \/>\n<meta property=\"og:site_name\" content=\"Virtual Servers VPS Blog\" \/>\n<meta property=\"article:published_time\" content=\"2026-09-12T22:39:53+00:00\" \/>\n<meta name=\"author\" content=\"Virtual-Servers-Vps-Editor\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Virtual-Servers-Vps-Editor\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"5 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/virtualserversvps.com\/blog\/tuning-postgresql-small-vps-shared-buffers-wal-max-connections\/\",\"url\":\"https:\/\/virtualserversvps.com\/blog\/tuning-postgresql-small-vps-shared-buffers-wal-max-connections\/\",\"name\":\"Tuning PostgreSQL for a Small VPS: shared_buffers, WAL, and max_connections - Virtual Servers VPS Blog\",\"isPartOf\":{\"@id\":\"https:\/\/virtualserversvps.com\/blog\/#website\"},\"datePublished\":\"2026-09-12T22:39:53+00:00\",\"author\":{\"@id\":\"https:\/\/virtualserversvps.com\/blog\/#\/schema\/person\/82a299a8284a66ff49f97c74684724a0\"},\"breadcrumb\":{\"@id\":\"https:\/\/virtualserversvps.com\/blog\/tuning-postgresql-small-vps-shared-buffers-wal-max-connections\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/virtualserversvps.com\/blog\/tuning-postgresql-small-vps-shared-buffers-wal-max-connections\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/virtualserversvps.com\/blog\/tuning-postgresql-small-vps-shared-buffers-wal-max-connections\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/virtualserversvps.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Tuning PostgreSQL for a Small VPS: shared_buffers, WAL, and max_connections\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/virtualserversvps.com\/blog\/#website\",\"url\":\"https:\/\/virtualserversvps.com\/blog\/\",\"name\":\"Virtual Servers VPS Blog\",\"description\":\"\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/virtualserversvps.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\/\/virtualserversvps.com\/blog\/#\/schema\/person\/82a299a8284a66ff49f97c74684724a0\",\"name\":\"Virtual-Servers-Vps-Editor\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/virtualserversvps.com\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/d820b15f1cd028e97610d9adf536df7be5cb6423869967037d468d5355fa003f?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/d820b15f1cd028e97610d9adf536df7be5cb6423869967037d468d5355fa003f?s=96&d=mm&r=g\",\"caption\":\"Virtual-Servers-Vps-Editor\"},\"sameAs\":[\"https:\/\/virtualserversvps.com\/blog\"],\"url\":\"https:\/\/virtualserversvps.com\/blog\/author\/virtualserversvps\/\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Tuning PostgreSQL for a Small VPS: shared_buffers, WAL, and max_connections - Virtual Servers VPS Blog","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/virtualserversvps.com\/blog\/tuning-postgresql-small-vps-shared-buffers-wal-max-connections\/","og_locale":"en_US","og_type":"article","og_title":"Tuning PostgreSQL for a Small VPS: shared_buffers, WAL, and max_connections","og_description":"Tuning PostgreSQL for a Small VPS: shared_buffers, WAL, and max_connections","og_url":"https:\/\/virtualserversvps.com\/blog\/tuning-postgresql-small-vps-shared-buffers-wal-max-connections\/","og_site_name":"Virtual Servers VPS Blog","article_published_time":"2026-09-12T22:39:53+00:00","author":"Virtual-Servers-Vps-Editor","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Virtual-Servers-Vps-Editor","Est. reading time":"5 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/virtualserversvps.com\/blog\/tuning-postgresql-small-vps-shared-buffers-wal-max-connections\/","url":"https:\/\/virtualserversvps.com\/blog\/tuning-postgresql-small-vps-shared-buffers-wal-max-connections\/","name":"Tuning PostgreSQL for a Small VPS: shared_buffers, WAL, and max_connections - Virtual Servers VPS Blog","isPartOf":{"@id":"https:\/\/virtualserversvps.com\/blog\/#website"},"datePublished":"2026-09-12T22:39:53+00:00","author":{"@id":"https:\/\/virtualserversvps.com\/blog\/#\/schema\/person\/82a299a8284a66ff49f97c74684724a0"},"breadcrumb":{"@id":"https:\/\/virtualserversvps.com\/blog\/tuning-postgresql-small-vps-shared-buffers-wal-max-connections\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/virtualserversvps.com\/blog\/tuning-postgresql-small-vps-shared-buffers-wal-max-connections\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/virtualserversvps.com\/blog\/tuning-postgresql-small-vps-shared-buffers-wal-max-connections\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/virtualserversvps.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Tuning PostgreSQL for a Small VPS: shared_buffers, WAL, and max_connections"}]},{"@type":"WebSite","@id":"https:\/\/virtualserversvps.com\/blog\/#website","url":"https:\/\/virtualserversvps.com\/blog\/","name":"Virtual Servers VPS Blog","description":"","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/virtualserversvps.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/virtualserversvps.com\/blog\/#\/schema\/person\/82a299a8284a66ff49f97c74684724a0","name":"Virtual-Servers-Vps-Editor","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/virtualserversvps.com\/blog\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/d820b15f1cd028e97610d9adf536df7be5cb6423869967037d468d5355fa003f?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/d820b15f1cd028e97610d9adf536df7be5cb6423869967037d468d5355fa003f?s=96&d=mm&r=g","caption":"Virtual-Servers-Vps-Editor"},"sameAs":["https:\/\/virtualserversvps.com\/blog"],"url":"https:\/\/virtualserversvps.com\/blog\/author\/virtualserversvps\/"}]}},"_links":{"self":[{"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/posts\/1114","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/comments?post=1114"}],"version-history":[{"count":1,"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/posts\/1114\/revisions"}],"predecessor-version":[{"id":1117,"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/posts\/1114\/revisions\/1117"}],"wp:attachment":[{"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/media?parent=1114"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/categories?post=1114"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/tags?post=1114"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}