Un backup que no se ha restaurado nunca no es un backup: es una esperanza con timestamp. En esta guía verás cómo diseñar una estrategia completa de backup de PostgreSQL a S3: desde el sencillo pg_dump hasta pgBackRest con archivado de WAL para recuperación en cualquier punto en el tiempo, cifrado, automatización con cron, inmutabilidad anti-ransomware con Object Lock y, lo más importante, cómo probar de verdad que tus copias restauran.
Lógico vs. físico: dos filosofías de copia
Antes de tocar un comando conviene entender que en PostgreSQL existen dos familias de backup, y no compiten tanto como se complementan.
- Backup lógico (
pg_dump,pg_dumpall): exporta el contenido de la base como sentencias SQL o como un archivo de formato personalizado. Es portable entre versiones mayores y entre arquitecturas, ideal para clonar una base concreta o migrar. Su talón de Aquiles es el tiempo: en bases grandes, volcar y restaurar puede llevar horas y no permite recuperar a un segundo exacto. - Backup físico (copia de los ficheros del clúster + archivado de WAL): copia los datos a nivel de bloque y conserva el registro de transacciones (Write-Ahead Log). Permite Point-in-Time Recovery (PITR), restauraciones mucho más rápidas en volúmenes grandes y backups incrementales. Es el enfoque que usan herramientas como pgBackRest o WAL-G y el recomendado en producción.
Una regla práctica: por debajo de unos pocos GB, pg_dump a S3 te resuelve la vida; a partir de ahí, o si necesitas un RPO (objetivo de punto de recuperación) de minutos, vete directo a pgBackRest.
pg_dump y pg_dumpall: el volcado lógico
pg_dump exporta una única base de datos; pg_dumpall añade los objetos globales del clúster (roles, tablespaces y permisos) que pg_dump no incluye. En la práctica querrás ambos: el dump de cada base y un volcado de globales.
El formato custom (-Fc) es el más útil: va comprimido, permite restauración selectiva de tablas y se restaura con pg_restore en paralelo. Puedes subirlo directamente a tu bucket en streaming, sin escribir en disco intermedio:
#!/usr/bin/env bash
set -euo pipefail
FECHA=$(date +%F)
ENDPOINT=https://s3.otterstorage.io
BUCKET=backups-pg
# Volcado de una base en formato custom comprimido, directo a S3
pg_dump -Fc -Z6 midb \
| aws s3 cp - "s3://${BUCKET}/midb/midb-${FECHA}.dump" \
--endpoint-url "${ENDPOINT}"
# Globales del clúster (roles, permisos, tablespaces)
pg_dumpall --globals-only \
| aws s3 cp - "s3://${BUCKET}/globals/globals-${FECHA}.sql" \
--endpoint-url "${ENDPOINT}"
Para configurar las credenciales del cliente, sigue la guía de access keys aisladas por bucket: lo ideal es que la clave que usa este script solo tenga permiso sobre backups-pg y nada más. Puedes apoyarte en la documentación de AWS CLI para los detalles del perfil.
Cifrar antes de subir
Tus backups contienen datos sensibles, así que cífralos en origen (cliente) para que viajen y reposen cifrados. La forma más simple es cifrar el flujo con GnuPG antes de enviarlo:
# Cifrado simétrico con AES-256 antes de subir
pg_dump -Fc midb \
| gpg --symmetric --cipher-algo AES256 --batch --passphrase-file /etc/pg/backup.key \
| aws s3 cp - s3://backups-pg/midb/midb-$(date +%F).dump.gpg \
--endpoint-url https://s3.otterstorage.io
Si prefieres no gestionar tú la criptografía, restic cifra de serie con AES-256 y autenticación Poly1305, y deduplica. Más abajo lo usamos como destino. Revisa también la guía de seguridad para el modelo completo.
pgBackRest con repositorio S3 (configuración completa)
pgBackRest es el estándar de facto para backup físico de PostgreSQL: gestiona copias completas, diferenciales e incrementales, comprueba checksums, comprime, cifra y guarda directamente en un repositorio S3 como OtterStorage. Una stanza es la unidad de configuración que asocia un clúster con su repositorio.
El fichero pgbackrest.conf
[global]
# Repositorio en S3 (OtterStorage)
repo1-type=s3
repo1-s3-endpoint=s3.otterstorage.io
repo1-s3-bucket=backups-pg
repo1-s3-region=eu-mad
repo1-s3-key=AKIAOTTEREXAMPLE
repo1-s3-key-secret=clave_secreta_aqui
repo1-s3-uri-style=path
repo1-path=/pgbackrest
# Cifrado del repositorio del lado del cliente
repo1-cipher-type=aes-256-cbc
repo1-cipher-pass=frase_larga_y_aleatoria
# Retención: conservar 4 backups completos
repo1-retention-full=4
# Compresión y rendimiento
compress-type=zst
compress-level=3
process-max=4
start-fast=y
[midb]
pg1-path=/var/lib/postgresql/16/main
pg1-port=5432
El parámetro repo1-s3-uri-style=path es importante con endpoints S3 compatibles: fuerza el estilo endpoint/bucket en lugar del estilo virtual-host. Usa eu-mad (Madrid), eu-fra (Frankfurt) o us-east según dónde quieras la copia.
Archivado de WAL para PITR
Para poder restaurar a cualquier segundo necesitas que PostgreSQL envíe sus segmentos WAL al repositorio de forma continua. Configura en postgresql.conf:
archive_mode = on
archive_command = 'pgbackrest --stanza=midb archive-push %p'
archive_timeout = 60
max_wal_senders = 3
wal_level = replica
Recarga la configuración, crea la stanza y verifica que todo encaja antes de confiar en ello:
# Crear el repositorio y la stanza
pgbackrest --stanza=midb stanza-create
# Comprobar que archive-push y la conexión a S3 funcionan
pgbackrest --stanza=midb check
Lanzar y programar los backups
# Backup completo (semanal)
pgbackrest --stanza=midb --type=full backup
# Backup diferencial (diario): solo lo cambiado desde el último full
pgbackrest --stanza=midb --type=diff backup
# Backup incremental (cada hora): solo lo cambiado desde el anterior
pgbackrest --stanza=midb --type=incr backup
# Ver el estado del repositorio
pgbackrest --stanza=midb info
Programar con cron
La automatización es lo que convierte un script en una política de backup. Un esquema típico con pgBackRest combina un completo semanal, diferenciales diarios e incrementales cada hora:
# /etc/cron.d/pgbackrest
# m h dom mon dow usuario comando
0 2 * * 0 postgres pgbackrest --stanza=midb --type=full backup
0 2 * * 1-6 postgres pgbackrest --stanza=midb --type=diff backup
0 * * * * postgres pgbackrest --stanza=midb --type=incr backup
# Restauración de verificación, primer día de cada mes a las 5:00
0 5 1 * * postgres /opt/scripts/verificar-restore.sh
Con el modelo de OtterStorage sin coste por peticiones (PUT/GET/LIST) ni por borrados, lanzar incrementales frecuentes o subir miles de segmentos WAL no infla la factura: solo pagas por TB almacenado. Eso cambia el cálculo respecto a otros proveedores donde cada operación cuenta.
Subir con rclone o restic a un bucket
Si haces backup lógico y quieres una capa de sincronización o cifrado y deduplicación, rclone y restic son excelentes complementos.
rclone para sincronizar volcados
Configura un remoto apuntando al endpoint y sincroniza tu directorio de dumps:
# ~/.config/rclone/rclone.conf
[otter]
type = s3
provider = Other
endpoint = https://s3.otterstorage.io
region = eu-mad
access_key_id = AKIAOTTEREXAMPLE
secret_access_key = clave_secreta_aqui
# Subir los volcados locales y eliminar en destino los que ya no existen en origen
rclone sync /var/backups/pg otter:backups-pg/midb \
--transfers 8 --checksum --progress
Tienes los detalles en la guía de rclone.
restic para snapshots cifrados y deduplicados
export RESTIC_REPOSITORY="s3:https://s3.otterstorage.io/backups-pg/restic"
export RESTIC_PASSWORD_FILE="/etc/pg/restic.pass"
export AWS_ACCESS_KEY_ID="AKIAOTTEREXAMPLE"
export AWS_SECRET_ACCESS_KEY="clave_secreta_aqui"
# Inicializar el repositorio una vez
restic init
# Backup en streaming del dump (sin tocar disco), con etiqueta
pg_dump -Fc midb | restic backup --stdin --stdin-filename midb.dump --tag postgresql
# Política de retención: 7 diarios, 4 semanales, 6 mensuales
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune
Consulta la guía de restic para afinar la retención. Restic cifra todo en cliente, así que ni siquiera nosotros podríamos leer tus snapshots.
pg_dump vs. pgBackRest: cuándo cada uno
| Criterio | pg_dump / pg_dumpall | pgBackRest |
|---|---|---|
| Tipo de copia | Lógica (SQL / formato custom) | Física + archivado de WAL |
| PITR (punto en el tiempo) | No | Sí, a un segundo exacto |
| Incrementales / diferenciales | No, siempre completo | Sí, completo, diferencial e incremental |
| Rendimiento en bases grandes | Lento (horas) | Rápido y paralelizable |
| Repositorio S3 nativo | Vía AWS CLI, rclone o restic | Sí, integrado (repo1-type=s3) |
| Portabilidad entre versiones mayores | Alta | Misma versión mayor |
| Cifrado y checksums | Manual (GPG / restic) | Integrado |
| Mejor caso de uso | Bases pequeñas, migraciones, clonado | Producción, RPO bajo, grandes volúmenes |
Inmutabilidad con Object Lock contra ransomware
El ransomware moderno busca primero tus backups: si los cifra o borra, no tienes alternativa al rescate. La defensa es hacer que las copias sean inmutables. Activa Object Lock (WORM) en el bucket de backups con OtterVault: durante el periodo de retención, ni un atacante con tus credenciales podrá sobrescribir ni borrar los objetos.
OtterStorage ofrece dos modos. Governance permite a usuarios con un permiso especial saltarse la retención (útil para corregir errores); Compliance no admite excepciones ni siquiera para la cuenta raíz, lo que cumple los requisitos de inmutabilidad regulatoria. Para protección a nivel de bucket completo dispones además de Legal Hold.
# Crear el bucket con versionado y Object Lock activado (requisito de WORM)
aws s3api create-bucket --bucket backups-pg \
--object-lock-enabled-for-bucket \
--endpoint-url https://s3.otterstorage.io
# Retención por defecto: 30 días en modo Compliance
aws s3api put-object-lock-configuration --bucket backups-pg \
--object-lock-configuration '{"ObjectLockEnabled":"Enabled","Rule":{"DefaultRetention":{"Mode":"COMPLIANCE","Days":30}}}' \
--endpoint-url https://s3.otterstorage.io
Object Lock exige versionado activo en el bucket. Profundiza en la guía de Object Lock y en versionado; para retención automática de WAL antiguos, combina lo anterior con reglas de lifecycle.
Probar la restauración (lo que de verdad importa)
Repite con nosotros: una restauración no probada es deuda técnica. Programa restauraciones periódicas en un entorno aislado y comprueba que la base levanta y los datos cuadran. Para un volcado lógico:
# Descargar y restaurar a una base de verificación
aws s3 cp s3://backups-pg/midb/midb-2026-06-11.dump - \
--endpoint-url https://s3.otterstorage.io \
| pg_restore --clean --if-exists -d midb_restore
# Comprobación rápida de integridad
psql -d midb_restore -c "SELECT count(*) FROM clientes;"
Con pgBackRest la restauración con PITR es directa: recuperas el clúster y aplicas el WAL hasta el instante elegido.
# Restaurar a un momento exacto antes de un borrado accidental
pgbackrest --stanza=midb \
--type=time "--target=2026-06-11 14:25:00+02" \
--delta restore
# Después, en postgresql.auto.conf pgBackRest deja recovery_target;
# arranca PostgreSQL y deja que reproduzca el WAL hasta ese punto
pg_ctl start
Documenta el tiempo que tarda (tu RTO real) y automatiza la verificación mensual con el script que añadimos antes al cron. Sigue el principio 3-2-1: tres copias, en dos medios, una fuera de tu infraestructura. S3 con Object Lock cubre esa copia inmutable externa. Si vienes de otro proveedor, OtterBridge ayuda con la migración asistida y OtterSync replica entre regiones para tener tu copia fuera de sitio en EU-FRA o US-EAST.
Echa un vistazo a la solución de backups y a los precios por TB (HDD 8 €, SSD 16 €, NVMe 45 €, sin coste por peticiones) para dimensionar tu estrategia.
Preguntas frecuentes
¿pg_dump o pgBackRest para producción? +
¿Cuánto cuesta enviar tantos WAL e incrementales a S3? +
¿Cómo protejo los backups contra ransomware? +
Backups inmutables para tus bases de datos
Object Lock, retención y sin coste por peticiones.
Hazte Founding Otter