Software Development Simplified

Self-Hosting PostgreSQL in Debian with SSL for Public Exposure

By Dario on May 15, 2025
PostgreSQL with SSL on Debian

Self-Hosting PostgreSQL in Debian with SSL for Public Exposure

In today’s data-driven world, having control over your database infrastructure is becoming increasingly important. While cloud-based database services offer convenience, self-hosting PostgreSQL gives you complete control over your data, configuration, and security measures.

Are you concerned about database security when exposing PostgreSQL to the internet? You’re right to be cautious. Unencrypted database connections can expose sensitive data to interception, potentially leading to data breaches. Fortunately, with proper SSL implementation, you can create a secure, publicly accessible PostgreSQL server that maintains data confidentiality and integrity.

This guide will walk you through the process of setting up a PostgreSQL server on Debian, securing it with SSL certificates, and configuring it for secure remote access. We’ll use Let’s Encrypt for SSL certificates and implement automatic renewal using certbot hooks to ensure your database remains secure with minimal maintenance.

By the end of this tutorial, you’ll have:

  • A fully functional PostgreSQL 17 server running on Debian
  • Properly configured SSL encryption for all remote connections
  • Automated certificate renewal that doesn’t interrupt database operations
  • A secure authentication system for remote users

Prerequisites

Before we begin, ensure you have:

  • A Debian-based server (this guide uses Debian 11 Bullseye or Debian 12 Bookworm)
  • Root or sudo access to the server
  • A domain name pointing to your server (for SSL certificate generation)
  • Basic knowledge of Linux command line and networking concepts

Step 1: Installing PostgreSQL

Let’s start by installing PostgreSQL from the official PostgreSQL repository to ensure we get the latest version:

# Install prerequisites
sudo apt install curl ca-certificates

# Add the PostgreSQL signing key
sudo install -d /usr/share/postgresql-common/pgdg
sudo curl -o /usr/share/postgresql-common/pgdg/apt.postgresql.org.asc --fail https://www.postgresql.org/media/keys/ACCC4CF8.asc

# Add the PostgreSQL repository
. /etc/os-release
sudo sh -c "echo 'deb [signed-by=/usr/share/postgresql-common/pgdg/apt.postgresql.org.asc] https://apt.postgresql.org/pub/repos/apt $VERSION_CODENAME-pgdg main' > /etc/apt/sources.list.d/pgdg.list"

# Update package lists
sudo apt update

# Install PostgreSQL 17 and related packages
sudo apt -y install postgresql-17 postgresql-contrib-17

This installation method ensures you get the latest PostgreSQL version directly from the official PostgreSQL repository, rather than using the potentially older version in the Debian repositories.

Verify the installation by checking the service status:

sudo systemctl status postgresql@17-main

You should see that the service is active and running.

Step 2: Basic PostgreSQL Configuration

Before we configure SSL, let’s perform some basic setup steps.

Accessing PostgreSQL

Switch to the postgres user and access the PostgreSQL command-line interface:

sudo -i -u postgres
psql -p 5432

Setting a Password for the postgres User

Within the PostgreSQL prompt, set a strong password for the postgres user:

ALTER USER postgres WITH PASSWORD 'your_secure_password';

Creating a New Database and User

For better security practices, create a dedicated database and user for your application:

CREATE DATABASE myappdb;
CREATE USER myappuser WITH ENCRYPTED PASSWORD 'another_secure_password';
GRANT ALL PRIVILEGES ON DATABASE myappdb TO myappuser;

Exit the PostgreSQL prompt:

\q

Step 3: Configuring PostgreSQL for Network Access and SSL

By default, PostgreSQL only listens on localhost. We need to modify its configuration to accept external connections and use SSL.

Edit the PostgreSQL configuration file:

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

Find and modify the following parameters:

listen_addresses = '*'  # Listen on all interfaces
ssl = on                # Enable SSL
ssl_cert_file = 'fullchain.pem'  # Let's Encrypt certificate
ssl_key_file = 'privkey.pem'     # Let's Encrypt private key

Next, configure client authentication by editing the pg_hba.conf file:

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

Add the following lines at the end of the file to allow SSL connections:

# Allow SSL connections from any IP for specific users
hostssl all         myappuser     0.0.0.0/0               md5
hostssl all         postgres      0.0.0.0/0               md5

# Optional: Allow local connections without SSL
host    all         all           127.0.0.1/32            md5
host    all         all           ::1/128                 md5

This configuration:

  • Requires SSL for all remote connections
  • Allows the specified users to connect from any IP address with password authentication
  • Allows local connections without SSL (optional)

Step 4: Setting Up SSL with Let’s Encrypt and Automatic Renewal

We’ll use Let’s Encrypt to generate free, trusted SSL certificates and set up automatic renewal with a custom hook script to update PostgreSQL certificates.

First, install certbot:

sudo apt install certbot

Generate the initial certificates:

sudo certbot certonly --standalone -d postgres.mydomain.com

Now, create a renewal hook script that will automatically update the PostgreSQL certificates when Let’s Encrypt renews them:

sudo nano /etc/letsencrypt/renewal-hooks/deploy/postgresql-cert-update.sh

Add the following script:

#!/bin/bash

DOMAIN="postgres.mydomain.com"
PG_VERSION="17"
PGDATA="/var/lib/postgresql/$PG_VERSION/main"
CERT_PATH="/etc/letsencrypt/live/$DOMAIN"
FULLCHAIN_FILE="$CERT_PATH/fullchain.pem"
PRIVKEY_FILE="$CERT_PATH/privkey.pem"
TEMP_FULLCHAIN="$PGDATA/fullchain.pem-1"
TEMP_PRIVKEY="$PGDATA/privkey.pem-1"
FINAL_FULLCHAIN="$PGDATA/fullchain.pem"
FINAL_PRIVKEY="$PGDATA/privkey.pem"

/usr/sbin/apache2ctl graceful
cp "$FULLCHAIN_FILE" "$TEMP_FULLCHAIN"
cp "$PRIVKEY_FILE" "$TEMP_PRIVKEY"
chmod 600 "$TEMP_FULLCHAIN" "$TEMP_PRIVKEY"
chown postgres:postgres "$TEMP_FULLCHAIN" "$TEMP_PRIVKEY"
mv -f "$TEMP_FULLCHAIN" "$FINAL_FULLCHAIN"
mv -f "$TEMP_PRIVKEY" "$FINAL_PRIVKEY"
pg_ctlcluster "$PG_VERSION" main reload

The last line of the script uses pg_ctlcluster with the reload parameter instead of restart. This is an important distinction:

  • reload tells PostgreSQL to re-read its configuration files without disconnecting clients or interrupting operations
  • Unlike a full restart, which would terminate all connections and potentially disrupt applications
  • This approach allows certificate updates to take effect while maintaining database availability
  • It’s particularly valuable in production environments where downtime should be minimized

Make the script executable:

sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/postgresql-cert-update.sh

Run the script once to copy the initial certificates to the PostgreSQL data directory:

sudo /etc/letsencrypt/renewal-hooks/deploy/postgresql-cert-update.sh

Restart PostgreSQL to apply all changes:

sudo systemctl restart postgresql@17-main

Step 5: Configuring Firewall

If you’re using a firewall (which you should), allow connections to the PostgreSQL port:

# For UFW
sudo ufw allow 5432/tcp

# For iptables
sudo iptables -A INPUT -p tcp --dport 5432 -j ACCEPT
sudo iptables-save > /etc/iptables/rules.v4

Step 6: Testing the Connection

From another machine, test the connection using the PostgreSQL client:

psql "host=postgres.mydomain.com port=5432 dbname=myappdb user=myappuser sslmode=require"

If everything is configured correctly, you should be prompted for a password and then connected to the database.

Advanced Security Considerations

For production environments, consider additional security measures like:

  • Implementing IP-based access restrictions
  • Setting up database replication for high availability
  • Using more advanced authentication methods
  • Regular security audits and penetration testing

Key Takeaways

  • SSL is essential when exposing PostgreSQL to the internet - never use unencrypted connections for remote access
  • Let’s Encrypt provides free, trusted certificates that are automatically renewed every 90 days
  • Certificate renewal hooks allow for seamless updates without database downtime
  • The reload parameter updates PostgreSQL configuration without disconnecting clients
  • Proper file permissions (600) and ownership (postgres user) are critical for SSL security
  • Client authentication configuration (pg_hba.conf) determines which connections require SSL

Conclusion

You now have a secure PostgreSQL 17 server running on Debian, accessible remotely via SSL-encrypted connections. The setup includes automatic certificate renewal through Let’s Encrypt and certbot hooks, ensuring your SSL certificates are always up-to-date.

Remember that database security is an ongoing process. Regularly update your system, review access logs, and adjust security measures as needed to protect your valuable data.

By following these practices, you’ll have a robust, secure PostgreSQL deployment that meets the needs of your applications while keeping your data safe from unauthorized access.

Troubleshooting

Connection Issues

If you’re having trouble connecting, check:

  • Firewall settings (both on the server and any network devices)
  • PostgreSQL logs: sudo tail -f /var/log/postgresql/postgresql-17-main.log
  • SSL certificate validity: openssl x509 -in /var/lib/postgresql/17/main/fullchain.pem -text -noout
  • Certificate permissions: Ensure certificates are owned by postgres user and have proper permissions

Certificate Renewal Issues

If certificate renewal isn’t working:

  • Check certbot logs: sudo journalctl -u certbot
  • Verify the renewal hook script is executable
  • Test the renewal process manually: sudo certbot renew --dry-run

Resources

Twitter iconLinkedIn iconGitHub iconYouTube icon
© 2025 Dario Griffo. All rights reserved.