Banco de Dados • 7 min de leitura

Backup que você sabe que funciona: do pg_dump ao S3 sem confiar na sorte

Backup que você sabe que funciona: do pg_dump ao S3 sem confiar na sorte

A maioria das pessoas descobre que o backup não funcionava durante a recuperação. Não antes. Não em um teste calmo numa tarde de quarta-feira — mas às 2h da manhã, com o banco de dados corrompido, o patrão no telefone e o arquivo de backup inexistente ou ilegível.

Este artigo não é sobre teoria. É sobre construir um sistema de backup que você consegue verificar, testar e confiar — com os comandos do Linux que já existem, sem ferramentas complexas, para um servidor web típico com banco de dados e arquivos de upload.

O que vamos cobrir

  • Os comandos essenciais: pg_dump, mysqldump, rsync, tar
  • Um script de backup completo e comentado: banco + arquivos + validação + notificação
  • Envio para S3 com rclone (funciona com AWS, Cloudflare R2, Backblaze B2, etc.)
  • Rotação automática sem ferramentas externas
  • Como testar a recuperação de verdade — o passo que quase todo mundo pula

Antes do script: entenda o que você está protegendo

Todo projeto web tem basicamente dois tipos de dado que precisam de backup:

Banco de dados — o estado da aplicação. Usuários, pedidos, configurações. Muda com alta frequência. Perder 1 hora de dados já pode ser crítico.

Arquivos — uploads de usuários, documentos gerados, imagens. Muda com frequência média. Geralmente maior em volume que o banco.

Cada um tem uma estratégia diferente. O banco precisa de consistência (dump atômico); os arquivos precisam de eficiência (cópia incremental).

Os comandos que importam

Backup de banco de dados

PostgreSQL:

# Dump simples em formato custom (comprimido, restaurável com pg_restore)
pg_dump -U postgres -d meu_banco -F c -f backup_$(date +%Y%m%d).dump

# Dump em SQL puro (legível, portável, maior)
pg_dump -U postgres -d meu_banco -f backup_$(date +%Y%m%d).sql

# Dump de todas as bases de uma vez
pg_dumpall -U postgres -f backup_all_$(date +%Y%m%d).sql

# Com senha sem prompt (use .pgpass em produção — não coloque senha no script)
# Crie ~/.pgpass: hostname:5432:meu_banco:usuario:senha
# chmod 600 ~/.pgpass

MySQL / MariaDB:

# Dump com estrutura e dados
mysqldump -u root -p meu_banco > backup_$(date +%Y%m%d).sql

# Dump sem senha no terminal (use ~/.my.cnf em produção)
# ~/.my.cnf:
# [mysqldump]
# user=root
# password=sua_senha
# chmod 600 ~/.my.cnf
mysqldump meu_banco > backup_$(date +%Y%m%d).sql

# Dump de todas as bases
mysqldump --all-databases > backup_all_$(date +%Y%m%d).sql

# Com compressão inline (economiza I/O)
mysqldump meu_banco | gzip > backup_$(date +%Y%m%d).sql.gz

Como verificar se o dump é válido:

# PostgreSQL: testa a integridade do arquivo custom sem restaurar
pg_restore --list backup.dump > /dev/null && echo "OK" || echo "CORROMPIDO"

# PostgreSQL: restaura em banco temporário para verificação real
createdb -U postgres teste_restore
pg_restore -U postgres -d teste_restore backup.dump
psql -U postgres -d teste_restore -c "SELECT COUNT(*) FROM usuarios;"
dropdb -U postgres teste_restore

# MySQL: verifica se o SQL está sintaticamente correto
mysql --verbose --dry-run < backup.sql 2>&1 | tail -5

Backup de arquivos com rsync

O rsync é eficiente porque só copia o que mudou. Com --link-dest, você cria snapshots que parecem completos mas compartilham arquivos inalterados via hardlinks — economizando disco sem sacrificar a capacidade de restaurar qualquer data.

# Cópia simples local
rsync -avz /var/www/uploads/ /backup/uploads/

# Para servidor remoto via SSH
rsync -avz -e "ssh -i ~/.ssh/backup_key" /var/www/uploads/ usuario@backup-server:/backup/uploads/

# Snapshot com hardlinks (cada dia parece um backup completo, mas compartilha arquivos)
HOJE=$(date +%Y%m%d)
ONTEM=$(date -d "yesterday" +%Y%m%d)
rsync -avz --link-dest=/backup/snapshots/$ONTEM /var/www/uploads/ /backup/snapshots/$HOJE/

# Exclui arquivos desnecessários
rsync -avz \
  --exclude='*.log' \
  --exclude='*.tmp' \
  --exclude='cache/' \
  --exclude='.git/' \
  /var/www/meusite/ /backup/site/

Compressão e verificação com tar

# Cria arquivo comprimido com timestamp
tar -czf backup_uploads_$(date +%Y%m%d).tar.gz /var/www/uploads/

# Verifica integridade sem descompactar
tar -tzf backup_uploads_$(date +%Y%m%d).tar.gz > /dev/null && echo "OK" || echo "CORROMPIDO"

# Extrai apenas um arquivo específico para verificação
tar -xzf backup_uploads.tar.gz var/www/uploads/foto_especifica.jpg -C /tmp/

# Com criptografia (AES-256 via openssl)
tar -czf - /var/www/uploads/ | openssl enc -aes-256-cbc -pbkdf2 -k "$BACKUP_PASSWORD" > backup_enc_$(date +%Y%m%d).tar.gz.enc

# Descriptografar
openssl enc -d -aes-256-cbc -pbkdf2 -k "$BACKUP_PASSWORD" -in backup_enc.tar.gz.enc | tar -xzf - -C /tmp/restaurado/

O script de backup completo

Este script cobre banco PostgreSQL + arquivos, comprime, envia para nuvem, verifica integridade, rotaciona os locais e envia notificação. Adapte as variáveis do topo para o seu ambiente.

Configurando o rclone para S3

O rclone funciona com AWS S3, Cloudflare R2, Backblaze B2, DigitalOcean Spaces e dezenas de outros. A configuração é feita uma vez:

# Instala rclone
curl https://rclone.org/install.sh | sudo bash

# Configura interativamente
rclone config
# Escolha: n (novo remote) → nome: s3-backup → tipo: s3 (ou r2, b2, etc.)
# Siga o wizard para inserir as credenciais

Para Cloudflare R2 (sem custo de egress — ótimo para backup):

# ~/.config/rclone/rclone.conf
[s3-backup]
type = s3
provider = Cloudflare
access_key_id = SUA_ACCESS_KEY
secret_access_key = SUA_SECRET_KEY
endpoint = https://SEU_ACCOUNT_ID.r2.cloudflarestorage.com
acl = private

Para AWS S3:

[s3-backup]
type = s3
provider = AWS
access_key_id = SUA_ACCESS_KEY
secret_access_key = SUA_SECRET_KEY
region = us-east-1
storage_class = STANDARD_IA   # Infrequent Access: mais barato para backup

Teste a configuração antes de depender dela:

rclone ls s3-backup:meu-bucket
rclone copy /tmp/teste.txt s3-backup:meu-bucket/teste/
rclone ls s3-backup:meu-bucket/teste/

Agendando com cron

sudo crontab -e
# Backup diário às 2h da manhã
0 2 * * * /usr/local/bin/backup.sh >> /var/log/backup_cron.log 2>&1

# Variáveis de ambiente necessárias (cron não carrega o ambiente do usuário)
BACKUP_ENCRYPTION_KEY=sua_chave_aqui
PGPASSFILE=/root/.pgpass

Alternativa mais robusta com systemd timer (não perde execuções se o servidor estava desligado):

# /etc/systemd/system/backup.service
[Unit]
Description=Backup diário do meusite
After=network.target postgresql.service

[Service]
Type=oneshot
User=root
ExecStart=/usr/local/bin/backup.sh
Environment="BACKUP_ENCRYPTION_KEY=sua_chave"
StandardOutput=journal
StandardError=journal
# /etc/systemd/system/backup.timer
[Unit]
Description=Timer do backup diário

[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true     # executa mesmo que o servidor estivesse desligado no horário

[Install]
WantedBy=timers.target
sudo systemctl enable --now backup.timer
sudo systemctl list-timers backup.timer  # confirma que está agendado

O teste que quase todo mundo pula

Configurar o backup é metade do trabalho. A outra metade é provar que ele funciona antes de precisar dele.

Faça isso uma vez por mês, em ambiente de staging:

Rotação de backups remotos no S3

Backups acumulam custo. Configure lifecycle policies no bucket para deletar automaticamente backups antigos:

AWS S3 via CLI:

# Mantém últimos 90 dias e deleta automaticamente
aws s3api put-bucket-lifecycle-configuration \
  --bucket meu-bucket \
  --lifecycle-configuration '{
    "Rules": [{
      "ID": "delete-old-backups",
      "Status": "Enabled",
      "Filter": {"Prefix": "meusite/"},
      "Expiration": {"Days": 90}
    }]
  }'

Via rclone (sem acesso ao console da nuvem):

# Delete backups remotos mais antigos que 90 dias
rclone delete s3-backup:meu-bucket/meusite \
  --min-age 90d \
  --rmdirs \
  --dry-run   # remova --dry-run quando tiver certeza

O que o script não cobre (e você deve considerar)

Backup de volumes Docker: se sua aplicação roda em containers, o pg_dump ainda funciona via docker exec:

docker exec meu-postgres pg_dump -U postgres meu_banco -F c > backup.dump

Replicação não é backup. Um banco replicado copia dados corrompidos ou deletados acidentalmente para todas as réplicas em segundos. Backup é cópia ponto-no-tempo; replicação é alta disponibilidade. Você precisa dos dois para propósitos diferentes.

Backups off-site geograficamente. Incêndio, enchente ou falha do data center afeta tudo na mesma região. Configure pelo menos um destino de backup em região diferente:

# Envia para dois destinos diferentes
rclone copy "$BACKUP_PATH" "s3-aws-sp:meu-bucket/meusite/${DATE}"
rclone copy "$BACKUP_PATH" "s3-r2-us:meu-bucket-dr/meusite/${DATE}"

Referências

Tags: backup, devops, postgresql, mysql, linux, rsync, rclone, bash, s3, automação