Backup criptografado e offsite da VPS

Backup não é opcional, é quando (não se) você vai precisar, seja por falha de hardware, ransomware, erro humano ou comprometimento do servidor. A regra 3-2-1: 3 cópias dos dados, 2 mídias diferentes, 1 cópia offsite. Na VPS, isso significa backup local + envio automático para object storage externo (S3, R2, ou similar) com criptografia.

Restic: backup incremental e criptografado

Restic é a melhor ferramenta para backup de arquivos de VPS, incremental, criptografado por padrão, deduplicado e suporta múltiplos destinos:

bash
# Instalar restic
apt install restic
# ou versão mais recente:
wget https://github.com/restic/restic/releases/latest/download/restic_linux_amd64.bz2
bunzip2 restic_linux_amd64.bz2 && mv restic_linux_amd64 /usr/local/bin/restic
chmod +x /usr/local/bin/restic

# Inicializar repositório de backup (com senha forte)
export RESTIC_PASSWORD="sua_senha_forte_aqui"
restic init --repo /opt/backup/local

# Para backup no S3/Cloudflare R2:
export AWS_ACCESS_KEY_ID="sua_access_key"
export AWS_SECRET_ACCESS_KEY="seu_secret_key"
restic init --repo s3:https://SEU_ENDPOINT_R2/nome-do-bucket/vps-backups

# Primeiro backup (backup completo)
restic backup /opt/app /etc /home --repo /opt/backup/local

# Backups subsequentes são automáticos e incrementais
restic snapshots --repo /opt/backup/local

Backup automático do PostgreSQL

Backup do banco separado dos arquivos, deve ser feito com pg_dump para consistência:

bash
#!/bin/bash
# /opt/scripts/backup-postgres.sh

set -euo pipefail

DB_NAME="meu_banco"
DB_USER="postgres"
BACKUP_DIR="/opt/backup/postgres"
DATE=$(date +%Y-%m-%d_%H-%M-%S)
BACKUP_FILE="$BACKUP_DIR/${DB_NAME}_${DATE}.sql.gz"

mkdir -p "$BACKUP_DIR"

# Dump comprimido
docker compose -f /opt/app/docker-compose.yml exec -T postgres \
  pg_dump -U "$DB_USER" "$DB_NAME" | gzip > "$BACKUP_FILE"

echo "Backup criado: $BACKUP_FILE ($(du -sh $BACKUP_FILE | cut -f1))"

# Enviar para S3/R2 com restic
export RESTIC_PASSWORD="$RESTIC_PASSWORD"
restic backup "$BACKUP_FILE" --repo s3:https://SEU_ENDPOINT/backups-db

# Manter apenas os últimos 7 backups locais
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +7 -delete

echo "Backup concluído: $BACKUP_FILE"

Política de retenção com restic

Defina por quanto tempo manter snapshots antigos para balancear espaço e cobertura:

bash
# Política de retenção: 7 diários, 4 semanais, 6 mensais
restic forget \
  --repo s3:https://SEU_ENDPOINT/backups \
  --keep-daily 7 \
  --keep-weekly 4 \
  --keep-monthly 6 \
  --prune

# Ver snapshots disponíveis
restic snapshots --repo s3:https://SEU_ENDPOINT/backups

# Verificar integridade do repositório (importante!)
restic check --repo s3:https://SEU_ENDPOINT/backups

# Cron para backup diário às 2h e forget semanal
# /etc/cron.d/restic-backup
# 0 2 * * * root /opt/scripts/backup-daily.sh
# 0 3 * * 0 root restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune --repo ...

Testar a restauração (obrigatório)

Backup não testado não é backup. Teste a restauração periodicamente:

bash
# Listar snapshots disponíveis
restic snapshots --repo s3:https://SEU_ENDPOINT/backups

# Restaurar snapshot específico em diretório temporário
restic restore SNAPSHOT_ID \
  --repo s3:https://SEU_ENDPOINT/backups \
  --target /tmp/restore-test

# Verificar que os arquivos foram restaurados
ls -la /tmp/restore-test/opt/app/

# Restaurar apenas arquivos específicos
restic restore latest \
  --repo s3:https://SEU_ENDPOINT/backups \
  --target /tmp/restore-test \
  --include "/opt/app/.env"

# Para banco: restaurar o dump e importar
gunzip -c /tmp/restore-test/opt/backup/postgres/db_2026-06-07.sql.gz | \
  docker compose exec -T postgres psql -U postgres meu_banco

Script completo de backup diário

Script de backup com notificação por e-mail em caso de falha:

bash
#!/bin/bash
# /opt/scripts/backup-daily.sh
set -euo pipefail

LOG_FILE="/var/log/backup-daily.log"
exec >> "$LOG_FILE" 2>&1

echo "=== Backup iniciado: $(date) ==="

# Variáveis de ambiente
source /opt/scripts/.backup-env

# 1. Backup do banco de dados
/opt/scripts/backup-postgres.sh

# 2. Backup de arquivos com restic
restic backup \
  /opt/app \
  /etc/nginx \
  /etc/letsencrypt \
  --repo "$RESTIC_REPO" \
  --exclude "node_modules" \
  --exclude ".git" \
  --tag "daily"

# 3. Política de retenção
restic forget \
  --repo "$RESTIC_REPO" \
  --keep-daily 7 --keep-weekly 4 \
  --prune --quiet

echo "=== Backup concluído: $(date) ==="

Não quer configurar manualmente?

Implante o VPS em menos de 5 minutos com a Runstack. Infraestrutura da OPEN Datacenter, com servidores em São Paulo.

Perguntas frequentes

Cloudflare R2: sem custo de egress, preço competitivo, compatível com S3 API, ideal para backups. AWS S3 com Glacier: muito barato para armazenamento frio, mas egress caro. Backblaze B2: barato e confiável. Para VPS no Brasil: R2 ou B2 têm melhor custo-benefício que S3.

Depende do RPO (Recovery Point Objective), quanto de dado você pode perder. Para apps de negócio: diário mínimo, horário para bancos críticos. A regra prática: calcule o custo de recriar 1 dia de dados vs. o custo do backup mais frequente. Geralmente backup diário + retenção de 30 dias é o mínimo aceitável.

rsync: simples, sem criptografia nativa, sem deduplicação, bom para sync entre servidores na mesma rede. restic: criptografado, deduplicado, incremental com versionamento, ideal para backup offsite. Para backup externo (S3/R2): sempre restic. Para sync entre servidores internos: rsync é suficiente.

pg_dump com docker compose exec -T (ou pg_dump direto) tira um snapshot consistente do banco sem bloquear operações. Para bancos muito grandes (>100GB): use pg_basebackup para backup de nível de blocos, mais rápido. Para clusters com streaming replication: backup da réplica não impacta o primário.

Armazene em /opt/scripts/.backup-env com chmod 600 e dono root. Nunca no .bashrc ou em repositório git. Para máxima segurança: use um secret manager (HashiCorp Vault, AWS Secrets Manager) e injete as credenciais via systemd EnvironmentFile. Guarde a RESTIC_PASSWORD offline, sem ela os backups são irrecuperáveis.