PostgreSQL Tuning Guide on DigitalOcean Droplet 2026
PostgreSQL ที่ติดตั้งบน Droplet ด้วยค่าเริ่มต้นจากแพ็กเกจ apt มักไม่ได้ถูกปรับแต่งให้เหมาะกับสเปกเครื่องจริง
PostgreSQL installed on a Droplet with default settings from the apt package is often not tuned to match the actual machine specifications, since PostgreSQL defaults are designed to run on machines with minimal RAM, not on Droplets with 2GB, 4GB or more RAM. This article explains the installation steps and tuning of PostgreSQL's main parameters on a Droplet with real command examples, including when you should stop managing it yourself and migrate to a Managed Database instead.
Contents
Installing PostgreSQL on a Droplet
Installing PostgreSQL on a Droplet running Ubuntu or Debian can be done directly via apt. Start by updating the package list with sudo apt update and then install with sudo apt install postgresql postgresql-contrib. The system will install both the postgresql server and the contrib extensions which come with standard extensions like pg_stat_statements. After installation completes, the service starts automatically. You can check the status with sudo systemctl status postgresql.
The next step is to set a password for the postgres system user by entering the psql shell first: sudo -u postgres psql and then run the command ALTER USER postgres WITH PASSWORD 'set your password here';. Inside the shell, exit with \q. The two main configuration files you need to know about are postgresql.conf which controls all instance parameters, and pg_hba.conf which controls which clients can connect and with which authentication method. Both files are located at /etc/postgresql/16/main/ on Ubuntu 24.04 (the version number in the path will change depending on which major version apt installs).
For a Droplet intended to run PostgreSQL seriously, you should choose a spec with at least 2GB RAM or more, because PostgreSQL needs memory for shared buffers, connections, and work memory for complex queries. The smallest Droplet with 512MiB RAM is only suitable for experimentation or learning. If you want to allow external clients to connect, you need to edit listen_addresses = '*' in postgresql.conf and add authorization lines in pg_hba.conf for specific IPs, along with opening Cloud Firewall only to port 5432 from trusted IPs. Never open port 5432 to the internet without IP restrictions, as it's a vulnerability that automated scanners search for constantly.
- Install with
sudo apt install postgresql postgresql-contrib - Check service status with
sudo systemctl status postgresql - Main config files are at
/etc/postgresql/<version>/main/ - Production Droplets should have at least 2GB RAM or more
- Never open port 5432 to the internet without IP restrictions
Basic Parameters to Tune: shared_buffers, work_mem
From our hands-on testing — postgreSQL's default values after a fresh installation are set very conservatively because they're designed to run even on machines with limited RAM. This means a Droplet with several GB of RAM won't use its resources to full capacity unless you manually adjust them. The first parameter you should adjust is shared_buffers, which is the memory PostgreSQL uses to cache table/index data in the process. The general guideline is to set it to about 25% of total RAM. For example, a 4GB Droplet ($24/month according to DigitalOcean Basic Droplet pricing) should set shared_buffers = 1GB, while a 2GB Droplet ($12/month) can be set to shared_buffers = 512MB.
The second parameter is effective_cache_size, which doesn't actually reserve memory but tells the query planner how much OS file cache (OS page cache) the system has available. This helps the planner make better decisions about whether to use an index instead of a sequential scan. The recommended value is 50-75% of RAM, such as effective_cache_size = 3GB on a 4GB Droplet.
The third parameter is work_mem, which is the memory per sort or hash operation in each query. This one requires special attention because a single query may use work_mem multiple times simultaneously (such as sort + hash join), and each connection is separate. If you set it too high on a server with many connections, RAM will run out and the OOM killer will terminate the postgres process. A safe starting value for typical Droplets is work_mem = 16MB to work_mem = 32MB. Meanwhile, maintenance_work_mem used when creating indexes or running VACUUM can be set higher, such as maintenance_work_mem = 256MB, because it runs infrequently and doesn't affect every connection.
After changing all values in the postgresql.conf file, you must reload or restart the service with sudo systemctl restart postgresql. Some parameter changes like shared_buffers require a full restart; reload isn't enough. You can verify the values in use after making changes with the psql command: SHOW shared_buffers;
shared_buffers≈ 25% of machine RAM, e.g., 1GB on a 4GB Dropleteffective_cache_size≈ 50-75% of RAMwork_memset low initially (16-32MB) to prevent RAM exhaustion with many connectionsmaintenance_work_memcan be set higher since it's only used during index creation/VACUUM
Indexes and Query Performance Basics
Server-level parameter tuning helps to a degree, but most real production performance problems come from queries without proper index support. The essential tool you need is EXPLAIN ANALYZE, which you put before a SELECT statement to see the actual execution plan along with the time spent at each step. For example, EXPLAIN ANALYZE SELECT * FROM orders WHERE customer_id = 42;. If the result shows Seq Scan on orders instead of Index Scan on a table with hundreds of thousands of rows, that's a sign you need to create an index.
Creating an index is simple with CREATE INDEX idx_orders_customer_id ON orders (customer_id);, but on tables with large amounts of data and ongoing traffic, a normal index creation will lock the table against writes during creation. You should use CREATE INDEX CONCURRENTLY instead to build the index without blocking writes, even if it takes slightly longer.
Another very useful tool is the pg_stat_statements extension that comes with postgresql-contrib. Enable it by adding the line shared_preload_libraries = 'pg_stat_statements' to postgresql.conf and then restart. After that, create the extension with CREATE EXTENSION pg_stat_statements;. Once enabled, you can query the pg_stat_statements table to see which queries are called most frequently and take the most total time, helping you focus optimization efforts on the queries that matter most, rather than guessing randomly.
Besides indexes, table statistics maintenance is equally important. PostgreSQL uses statistics from ANALYZE to decide on a query plan. If statistics are stale or don't match real data, the planner may choose a poor plan. Normally autovacuum runs ANALYZE automatically, but after loading large amounts of data at once (bulk import), you should run ANALYZE table_name; yourself immediately to update statistics without waiting for the next autovacuum cycle.
- Use
EXPLAIN ANALYZEto see the execution plan before guessing what the problem is - Seq Scan on large tables = sign that you need to create an index
- Create indexes on live tables with
CREATE INDEX CONCURRENTLY - Enable
pg_stat_statementsto find queries that consume the most total time
When to Migrate to Managed Database
Running PostgreSQL yourself on a Droplet gives you maximum flexibility and lowest cost initially, but there are points where you should consider migrating to DigitalOcean Managed Database instead, especially when your team starts spending more time on backup, version patching, and monitoring than you'd like. DigitalOcean's Managed PostgreSQL starts at the Basic plan with 1vCPU/1GiB RAM and 10-30GiB storage at $15.15 per month (prices as of July 2026, check the provider's website for current pricing), which is more expensive than a similarly-sized bare Droplet, but it includes daily automated backups, point-in-time recovery, automatic security patch updates, and the option to add standby nodes for high availability, which would be quite complex to set up yourself on a Droplet. Three main signals indicate it's time to migrate. First, when database downtime starts to have real business impact, such as on an e-commerce system that takes orders continuously. Having standby nodes with automatic failover in Managed Database significantly reduces this risk. Second, when your team lacks someone comfortable with database administration to handle vacuuming, index bloat, and security patches consistently. Managed Database handles these automatically. Third, when you need compliance or audit logging at a level that's difficult to configure on a self-hosted Droplet. Conversely, if your project is still in development, budget is limited, or your team already has PostgreSQL knowledge and needs fine-grained control of extensions/configuration (like using extensions that Managed Database doesn't support), managing PostgreSQL yourself on a Droplet still makes sense. Many teams choose a hybrid approach: dev/staging runs on Droplets themselves, while production migrates to Managed Database once the system has real users.
- Managed PostgreSQL starts at $15.15/month (1vCPU/1GiB RAM, 10-30GiB storage) — July 2026 pricing
- Includes automated backups, point-in-time recovery, automatic patching
- Suitable when downtime has real business impact or your team lacks a full-time DBA
- Self-hosting on Droplets is still more cost-effective if you're in dev/staging or have limited budget
- Hybrid approach: dev on Droplets, production on Managed Database
Backing Up with pg_dump and Volume Snapshots
From our hands-on testing — when you manage PostgreSQL yourself on a Droplet, backup responsibility falls entirely on your team. There's no built-in automated backup system like Managed Database provides. The most basic method is to use pg_dump to back up at the database level, such as pg_dump -U postgres mydb > mydb_backup.sql, or use the custom format which compresses better and restores more flexibly with pg_dump -U postgres -Fc mydb > mydb_backup.dump. Restore is done with pg_restore -U postgres -d mydb mydb_backup.dump. You should set up a cron job to run pg_dump automatically every day, for example, add a line to crontab -e: 0 2 * * * pg_dump -U postgres -Fc mydb > /backups/mydb_$(date +%F).dump, and you should also copy the backup files outside the Droplet, such as uploading them to DigitalOcean Spaces so backups don't disappear along with the Droplet.
Another layer you should use together is DigitalOcean Volumes and Volume Snapshots. If you keep PostgreSQL's data directory on a separate Volume (different from the Droplet's boot disk at a price of $0.10 per GiB per month), you can create snapshots of that Volume directly. Volume snapshots cost $0.06 per GiB per month, which can be cheaper than storing raw backup files in some cases, and snapshots are much faster than pg_dump for large databases because they work at the block storage level rather than exporting data row by row.
The caution is that Volume snapshots are crash-consistent, not application-consistent. If you create a snapshot while PostgreSQL is writing data, you might get a backup that needs to go through crash recovery when restored. A safer approach is to run SELECT pg_start_backup('snapshot'); before creating the snapshot, then follow up with SELECT pg_stop_backup(); after creation completes (or use pg_basebackup for a more comprehensive physical backup). The safest and easiest to verify approach for most teams is to use pg_dump as the primary backup method for data level, and use Volume Snapshots as an additional layer for fast system-wide recovery.
pg_dump -U postgres -Fc mydb > mydb_backup.dumpbacks up at the database level- Set up a cron job to run pg_dump daily and store backup files outside the Droplet
- Volume snapshots cost $0.06/GiB/month, much faster than pg_dump for large databases
- Volume snapshots are crash-consistent, should use pg_start_backup/pg_stop_backup together
When to Use This Feature (Real Use Cases)
Tuning PostgreSQL yourself on a Droplet is suitable for certain specific situations rather than being the best choice for every project. The first clear use case is small to medium applications where the team already has PostgreSQL knowledge and wants to control everything themselves, such as a SaaS just starting out that needs to minimize infrastructure costs in early stages. A 4GB Droplet at $24 per month that's properly tuned for PostgreSQL often handles thousands to tens of thousands of users, at a lower cost than Managed Database starting at $15.15/month for smaller specs. The second use case is teams that need to use PostgreSQL extensions that Managed Database doesn't support, or special configuration that requires tweaking kernel/filesystem parameters together, such as work that needs to coordinate PostgreSQL with specialized analytics systems, or teams doing custom database replication that need to control WAL and replication slots directly. The third use case is using it as a dev/staging environment that doesn't need high SLA. Running PostgreSQL on the same Droplet as the application, or a separate inexpensive Droplet, saves money compared to creating separate Managed Databases for every environment, and also trains your team to understand PostgreSQL behavior at a deeper level than always using a managed service. Meanwhile, use cases where we don't recommend this include systems where revenue directly depends on database uptime, or teams that don't have time to follow security patches and vacuuming consistently. In these cases, the cost savings from self-management are usually outweighed by risk and time lost when problems occur.
- Startups/apps that need cost-efficiency and teams with existing PostgreSQL knowledge
- Work requiring extensions or special configuration that Managed Database doesn't support
- Dev/staging environments that don't need high SLA
- Not suitable for systems where revenue depends on database uptime and teams lack maintenance time
Common Mistakes and Fixes
Based on real-world use, the most common mistake in tuning PostgreSQL on Droplets is setting shared_buffers or work_mem too high while forgetting to calculate how much RAM multiple connections will consume together. The result is the operating system calling the OOM killer to terminate postgres process mid-request, causing a database crash without warning. The fix is to calculate carefully, set max_connections to what you actually need (shouldn't exceed 100-200 for typical Droplets), and use a connection pooler like PgBouncer instead of opening many direct connections from the application.
The second mistake is a Droplet with low RAM (512MiB-1GiB) without swap enabled. When PostgreSQL or other processes exceed RAM, the system crashes immediately instead of degrading gracefully. You should enable at least 1-2GB of swap with fallocate -l 2G /swapfile && chmod 600 /swapfile && mkswap /swapfile && swapon /swapfile. Although swap isn't a long-term solution for heavy workloads, it helps prevent sudden crashes.
The third mistake is forgetting to monitor Droplet or Volume disk space where the data directory lives. When disk fills up, PostgreSQL immediately rejects writes and may corrupt in-flight transactions. You should set up an Alert Policy via DigitalOcean Monitoring (free) to warn when disk usage exceeds 80% well before the disk actually fills.
The fourth mistake is letting autovacuum fall behind the rate of data changes, causing table and index bloat to accumulate, making queries gradually slower without clear cause. You can check this with n_dead_tup in the pg_stat_user_tables table. If that number is unusually high compared to n_live_tup, adjust autovacuum_vacuum_scale_factor downward for tables that update frequently.
- shared_buffers/work_mem too high with many connections causes OOM killer — use PgBouncer to limit connections
- Low-RAM Droplets without swap crash instantly — enable 1-2GB swap to prevent sudden crashes
- Full disk rejects writes — set Alert Policy to warn at 80% disk usage
- Autovacuum falling behind causes bloat — check n_dead_tup and adjust autovacuum_vacuum_scale_factor
Best Practices
Good tuning must start with measurement before and after, not by feel. Use the pgbench tool that comes with postgresql-contrib to create a baseline benchmark with pgbench -i mydb, then run a test with pgbench -c 10 -j 2 -T 60 mydb and record the transactions per second. Then rerun after each parameter adjustment to compare. This way you know for certain whether an adjustment helps, rather than guessing.
You should adjust one parameter at a time and document the reasoning in your config file or commit message, especially if you keep postgresql.conf in version control. Changing multiple parameters at once makes it hard to find the culprit if performance gets worse instead of better. Also test changes on a staging Droplet before applying them to production.
Enable DigitalOcean Monitoring (free, no extra charge) to continuously monitor CPU, memory, and disk I/O of your Droplet. Set up Alert Policies for memory and disk usage in advance, because tuning mistakes usually show up as gradually rising memory usage before an actual crash. Catching the signal early helps fix problems before they cause real downtime.
Finally, you should plan a growth path in advance. You don't have to manage PostgreSQL on Droplets forever. Set clear metrics up front: when traffic exceeds a certain level or your team starts spending more time on database maintenance than feature development, switch to Managed Database. Planning this way makes the migration decision based on real data rather than a reaction to problems after an incident.
- Benchmark before/after with
pgbenchevery time you adjust, don't guess without measurement - Adjust one parameter at a time + document reasoning, test on staging first
- Enable free DigitalOcean Monitoring, set Alerts for memory and disk usage
Frequently Asked Questions
shared_buffers = 1GB is appropriate, then adjust up or down based on actual workload after testing with pgbench.SELECT pg_reload_conf(); or sudo systemctl reload postgresql. But parameters affecting shared memory like shared_buffers and max_connections require a full service restart.