Setting Up PostgreSQL on Ubuntu VPS

•5 min read
cover

Before the before you start

I've tried my best to detail everything here. If you notice any issues or things that are wrong, please reach out on X @ldo_dev or by email and let me know and I'll get them sorted asap.

If you think I can improve this in anyway, please let me know also!

Before you start

Buy a vps and figure out your ssh credentials

First Login

ssh into your new setup

ssh root@domain
 
# or
 
ssh root@ip

Update everything

sudo apt update && sudo apt upgrade -y

Set the timezone (optional/ depends where you're hosing)

timedatectl set-timezone Europe/London # Change for yours?

Create a non-root user (first time setup only)

adduser luke
usermod -aG sudo luke

From now on we use the new user

su - luke

Install Postgres

🤷🏼‍♀️

You can change the version to what you like. I think 18 is the latest, but all my apps are tailored for 17, so that's what I'll use.

Ubuntu repositories can lag - so best to use the official PostgreSQL repo

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/postgres.gpg
 
echo "deb [signed-by=/usr/share/keyrings/postgres.gpg] \
http://apt.postgresql.org/pub/repos/apt \
$(lsb_release -cs)-pgdg main" \
 | sudo tee /etc/apt/sources.list.d/postgres.list

Install updates and PostgreSQL

sudo apt update && sudo apt install -y postgres-17 postgres-client-17

Verify installation

psql --version

Sanity check it actually installed and is running

sudo systemctl status postgresql

Switch to the postgres user:

sudo -i -u postgres
psql

Exit postgres editor

\q # or "exit" if that doesn't work

Create production and dev roles and databases

🤷🏼‍♀️

Again this is just something I like to do. Production users can only access the production databases and vice versa. Helps to prevent any fuckups in dev lol

Re-enter postgres account

sudo -i -u postgres
psql

You're now in the postgreSQL editor.

Create Roles (users)

CREATE ROLE prod_user LOGIN PASSWORD 'STRONG_PROD_PASSWORD';
CREATE ROLE dev_user  LOGIN PASSWORD 'STRONG_DEV_PASSWORD';

Create databases

CREATE DATABASE prod_db OWNER prod_user;
CREATE DATABASE dev_db  OWNER dev_user;

Lock users to their db only

REVOKE ALL ON DATABASE prod_db FROM PUBLIC;
REVOKE ALL ON DATABASE dev_db  FROM PUBLIC;

Give users permission to see their db

ALTER DATABASE prod_db OWNER TO prod_user;
GRANT ALL PRIVILEGES ON DATABASE prod_db TO prod_user;

Exit Postgres Editor

\q # or "exit" if that doesn't work

Configure Postgres to accept remote connections

Edit postgresql.conf

sudo nano /etc/postgresql/17/main/postgresql.conf

Scroll through and find the following line, uncomment and change it

listen_address = '*'

Edit pg_hba.conf

🤷🏼‍♀️

This is your holy grail - treat it with care and don't tamper with it

sudo nano /etc/postgresql/17/main/pg_hba.conf

Add at the bottom

# PROD
hostssl prod_db prod_user 0.0.0.0/0 scram-sha-256
 
# DEV
hostssl dev_db dev_user  0.0.0.0/0 scram-sha-256

Notes:

  • hostssl forces TLS
  • scram-sha-256 is modern and good
  • Firewall will do the real IP locking
  • 0.0.0.0/0 allows any connection from any IP range. Ideally, once your main hosting is configured (if using a static IP), you'll only allow traffic from there to prevent attacks.

Every time you change pg_hba.conf you will need to restart postgres server

sudo systemctl restart postgresql

Enable TLS

Create the certificate directory

sudo mkdir /etc/postgresql/ssl
sudo chown postgres:postgres /etc/postgresql/ssl
sudo chmod 700 /etc/postgresql/ssl

Generate Self Signed Cert (fine for now)

sudo -i -u postgres
openssl req -new -x509 -days 365 \
 -nodes -text \
 -out /etc/postgresql/ssl/server.crt \
 -keyout /etc/postgresql/ssl/server.key
chmod 600 /etc/postgresql/ssl/server.key

Exit postgres

\q # or "exit" if that doesn't work

Tell postgres to use the cert

sudo nano /etc/postgresql/17/main/postgresql.conf

Set:

ssl = on
ssl_cert_file = '/etc/postgresql/ssl/server.crt'
ssl_key_file  = '/etc/postgresql/ssl/server.key'

Restart service again

sudo systemctl restart postgres

Enable Firewall

We'll use UFW (Universal FW)

# If you're allowing all IPs through from previous step, use:
sudo ufw default allow incoming
 
# Otherwise deny it and follow next steps
sudo ufw default deny incoming

Allow SSH

sudo ufw allow from [your_ip] to any port 22

Allow Web hosting thorough (my example)

sudo ufw allow from VERSEL_IP_RANGE to any port 5432
sudo ufw allow from SEVALLA_IP_RANGE to any port 5432

Regardless of which above option you selected, set outgoing to open

sudo ufw default allow outgoing

Enable ufw:

sudo ufw enable
sudo ufw status

Test connection locally

psql "postgresql://prod_user@localhost/prod_db?sslmode=require"

Connection string example

DATABASE_URL="postgresql://prod_user:PASS@prod.pg.your-infra.com:5432/prod_db?sslmode=require"
🤷🏼‍♀️

Note: you might have some issues with url encoding the password (i did). Should be fine, just generate a password that doesn't need encoding - your fav chat bot will do that.

Setup Backups

Install

sudo apt install -y postgresql-client

Setup cron job to run daily

crontab -e
0 2 * * * pg_dump prod_db | gzip > /var/backups/prod_db_$(date +\%F).sql.gz

Later if you run out of storage you can push this to another server, but for now just leave it in one place, it's easier.