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
- pg_dump — PostgreSQL Documentation
- mysqldump — MySQL Documentation
- rclone Documentation
- rsync man page
- OWASP — Backup and Recovery Cheat Sheet
Tags: backup, devops, postgresql, mysql, linux, rsync, rclone, bash, s3, automação