1,206 25/03/2026 07/04/2026 8 min

In produzione, il problema non è fare il backup. Il problema è scoprirlo rotto quando serve davvero.

Questo articolo mostra una configurazione concreta per PostgreSQL 15 con pgBackRest, backup verificabili, restore testato e un hardening minimo dei permessi. L’obiettivo è semplice: ripristinare in fretta, senza lasciare il database più aperto del necessario.

Il caso tipico è questo: backup giornalieri presenti, ma restore lento, ruoli troppo larghi su public, e un test DR mai eseguito. Qui correggiamo proprio quel punto.

Prerequisiti

  • PostgreSQL 15 su Linux.
  • pgBackRest installato su server database e repository backup.
  • Accesso da terminale come utente con privilegi sudo.
  • Spazio disco separato per repository o bucket S3 compatibile.
  • Una finestra di manutenzione per il primo test di restore.

Note: se usi Plesk o un pannello, la parte di PostgreSQL spesso è limitata. La configurazione di pgBackRest resta quasi sempre da riga di comando.

Step 1: separa ruoli e percorsi

La prima regola è non salvare il backup nello stesso volume dei dati. Se il disco si corrompe, perdi sia il database sia la copia.

Crea un utente dedicato e una directory di repository con permessi stretti.

sudo useradd --system --home /var/lib/pgbackrest --shell /usr/sbin/nologin pgbackrest
sudo mkdir -p /var/lib/pgbackrest /etc/pgbackrest /var/log/pgbackrest
sudo chown -R pgbackrest:pgbackrest /var/lib/pgbackrest /var/log/pgbackrest
sudo chmod 750 /var/lib/pgbackrest /var/log/pgbackrest

# Output: directory create e proprietà assegnata a pgbackrest

Per il database, controlla che il servizio PostgreSQL non scriva in una directory backup condivisa con altri servizi.

sudo -u postgres psql -c "SHOW data_directory;"
sudo -u postgres psql -c "SHOW config_file;"

# Output: percorsi di data_directory e config_file

Warning: non usare chmod 777 per “far funzionare” il backup. È un errore che torna utile solo ai problemi di sicurezza.

Step 2: configura pgBackRest per un profilo da produzione

La configurazione sotto punta a un repository locale. Se preferisci S3, il principio non cambia: cifratura, retention e verifica restano obbligatorie.

sudo tee /etc/pgbackrest/pgbackrest.conf > /dev/null <<'EOF'
[global]
repo1-path=/var/lib/pgbackrest
repo1-retention-full=7
repo1-retention-diff=14
log-level-console=info
start-fast=y
process-max=2
compress-type=zst
archive-async=y
archive-push-queue-max=8MB

[main]
pg1-path=/var/lib/postgresql/15/main
pg1-user=postgres
EOF

# Output: file di configurazione scritto in /etc/pgbackrest/pgbackrest.conf

Ogni parametro ha uno scopo preciso. compress-type=zst riduce spazio e tempi. archive-async=y aiuta l’archiving WAL. start-fast=y accelera il backup, ma va bene solo se il server ha margine I/O.

Se usi repository remoto, aggiungi cifratura lato backup. Con pgBackRest è meglio cifrare il repository che affidarsi solo alla rete.

sudo tee -a /etc/pgbackrest/pgbackrest.conf > /dev/null <<'EOF'
repo1-cipher-type=aes-256-cbc
repo1-cipher-pass=CAMBIA_QUESTA_PASSWORD_LUNGA
EOF

# Output: cifratura repository abilitata

Note: la password va conservata fuori dal server, in un secret manager o in un vault aziendale.

Step 3: limita i permessi su public e verifica i ruoli

Uno dei problemi più comuni in PostgreSQL è dare troppo a public. In produzione, il database deve essere prevedibile. Non “comodo”.

Controlla i privilegi correnti e rimuovi ciò che non serve.

sudo -u postgres psql -d appdb -c "\dn+"
sudo -u postgres psql -d appdb -c "REVOKE CREATE ON SCHEMA public FROM PUBLIC;"
sudo -u postgres psql -d appdb -c "REVOKE ALL ON DATABASE appdb FROM PUBLIC;"

# Output: privilegi ridotti su schema public e database

Se l’applicazione deve usare uno schema dedicato, assegna permessi espliciti al solo ruolo applicativo.

sudo -u postgres psql -d appdb -c "GRANT USAGE ON SCHEMA app TO app_user;"
sudo -u postgres psql -d appdb -c "GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA app TO app_user;"
sudo -u postgres psql -d appdb -c "ALTER DEFAULT PRIVILEGES IN SCHEMA app GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO app_user;"

# Output: ruolo app_user autorizzato solo sullo schema app

Questo evita il classico effetto collaterale: un restore andato bene, ma applicazione che poi scrive dove non dovrebbe.

Step 4: avvia un backup completo e controlla i WAL

Ora fai il primo backup completo. È il momento in cui scopri se la catena archivio-WAL funziona davvero.

sudo -u postgres pgbackrest --stanza=main stanza-create
sudo -u postgres pgbackrest --stanza=main backup

# Output: stanza creata e backup full completato

Subito dopo, verifica lo stato del repository.

sudo -u postgres pgbackrest --stanza=main info

# Output: almeno un backup full con stato ok

Se il backup fallisce con archiving sospeso, il problema è quasi sempre nel flusso WAL. In quel caso controlla i log PostgreSQL e il percorso di archiviazione.

sudo -u postgres psql -c "SHOW archive_mode;"
sudo -u postgres psql -c "SHOW archive_command;"

# Output: archive_mode attivo e archive_command configurato

Step 5: esegui un restore test in una directory separata

Il restore vero non va provato sul database di produzione. Va provato in una directory isolata, con un servizio temporaneo.

Ferma il cluster di test, prepara una directory vuota e ripristina lì dentro.

sudo systemctl stop postgresql
sudo rm -rf /var/lib/postgresql/15/test-restore
sudo mkdir -p /var/lib/postgresql/15/test-restore
sudo chown postgres:postgres /var/lib/postgresql/15/test-restore
sudo -u postgres pgbackrest --stanza=main restore --pg1-path=/var/lib/postgresql/15/test-restore

# Output: dati ripristinati in /var/lib/postgresql/15/test-restore

Avvia un’istanza di test solo se il tuo ambiente lo consente. In alternativa, monta la directory ripristinata e controlla integrità e checksum applicativi.

Note: il comando di restore completo è spesso disponibile solo da terminale. Nei pannelli grafici di solito non c’è un test DR reale.

Step 6: rendi il restore ripetibile con un runbook

Il restore non deve vivere nella memoria di una sola persona. Serve un runbook breve, eseguibile in emergenza.

Un runbook utile contiene tre cose: ordine dei comandi, tempo atteso, criterio di ok.

restore_runbook:
  stop_service: "systemctl stop postgresql"
  verify_repo: "pgbackrest --stanza=main info"
  restore_path: "/var/lib/postgresql/15/test-restore"
  validate: "SELECT count(*) FROM pg_class;"
  accept_if:
    - "restore completed without errors"
    - "database starts in less than 10 minutes"
    - "application login works with app_user"

# Output: runbook salvato e condivisibile con il team

Un runbook scritto bene riduce errori sotto stress. È un dettaglio operativo, ma in incident response fa la differenza.

Step 7: pianifica backup automatici e retention

In produzione il backup manuale non basta. Serve una pianificazione fissa, con retention coerente e spazio monitorato.

Con systemd timer o cron, esegui un full settimanale e diff giornalieri. Se usi cron:

sudo tee /etc/cron.d/pgbackrest > /dev/null <<'EOF'
15 2 * * 1 postgres pgbackrest --stanza=main backup --type=full
15 2 * * 2-7 postgres pgbackrest --stanza=main backup --type=diff
EOF

# Output: backup schedulati con full il lunedì e diff gli altri giorni

Questo schema è semplice e prevedibile. La retention deve essere allineata alla capacità del repository e al RPO richiesto.

Se il repository è su S3

Con un bucket remoto, aggiungi almeno questi accorgimenti:

  • bucket privato, mai pubblico;
  • access key con privilegi minimi;
  • versioning attivo;
  • cifratura lato server o lato client;
  • accesso solo da IP autorizzati, se possibile.

La sicurezza del backup è parte della sicurezza del database. Un backup leggibile da chiunque è un secondo incidente, non una protezione.

Verifica finale

La verifica finale deve essere pratica. Non basta vedere “success” nei log.

  1. Controlla che il repository contenga almeno un full e un diff recente.
  2. Conferma che il restore in directory separata sia andato a buon fine.
  3. Verifica che i privilegi su public siano stati ridotti.
  4. Simula l’accesso dell’app con il solo ruolo applicativo.
  5. Misura il tempo necessario per ripristinare un database di test.

Se vuoi un controllo rapido dei privilegi, usa questo comando:

sudo -u postgres psql -d appdb -c "\dp"
sudo -u postgres psql -d appdb -c "\dn+ public"

# Output: nessun privilegio eccessivo su public, accesso controllato ai soli ruoli previsti

Una buona baseline è questa: backup valido, restore testato, permessi ridotti, tempi misurati. Se manca uno di questi quattro punti, la configurazione non è ancora da produzione.

Troubleshooting

1) ERROR: [056]: archive-push command error: unable to open WAL file

Causa: PostgreSQL non riesce a scrivere o trovare i WAL per l’archiviazione.

Fix:

sudo -u postgres psql -c "SHOW archive_command;"
sudo tail -n 50 /var/log/postgresql/postgresql-15-main.log
sudo chown -R postgres:postgres /var/lib/postgresql/15/main

# Output: WAL access ripristinato e permessi corretti

2) ERROR: [082]: unable to load info file: permission denied

Causa: l’utente pgBackRest non ha accesso al repository o alla directory di log.

Fix:

sudo chown -R pgbackrest:pgbackrest /var/lib/pgbackrest /var/log/pgbackrest
sudo chmod 750 /var/lib/pgbackrest /var/log/pgbackrest
sudo -u postgres pgbackrest --stanza=main info

# Output: info repository leggibile e stato backup visibile

3) ERROR: permission denied for schema public

Causa: hai revocato i permessi a public, ma l’app usa ancora lo schema predefinito.

Fix:

sudo -u postgres psql -d appdb -c "ALTER ROLE app_user SET search_path = app, public;"
sudo -u postgres psql -d appdb -c "GRANT USAGE ON SCHEMA app TO app_user;"

# Output: l’app usa lo schema corretto senza toccare public in scrittura

Conclusione

Una configurazione seria di PostgreSQL non si misura dal numero di backup, ma dalla qualità del ripristino. pgBackRest aiuta molto, ma solo se il restore viene provato davvero.

Il prossimo passo concreto è fissare un restore test mensile con dataset realistico. Solo così saprai se il tuo piano di recupero regge il carico, i permessi e il tempo di fermo accettabile.