Бэкап «в поток» — когда данные не лежат файлами, а вычитываются командой: pg_dump, mysqldump, tar виртуальной машины. Restic умеет принимать такой поток напрямую в S3 с дедупликацией и шифрованием, и на одном сервере это настраивается за час. Но у схемы есть три ловушки, из-за которых люди годами живут с бэкапами, которые «вроде делаются»: restic не знает, что команда упала и сохраняет обрезанный дамп как успешный; монолитный репозиторий на всё сразу теряет и дедупликацию, и всю историю при порче метаданных; а восстановление «в трубу» идёт в разы медленнее, чем в каталог. Ниже — рабочий порядок, который этих ловушек избегает, с актуальными командами restic 0.19.1 (июль 2026) и скриптом для VPS на systemd.
Когда вообще нужен бэкап из stdin
Есть три типичных случая, и все они про данные, которые нельзя просто скопировать файлом.
Дамп базы. PostgreSQL и MySQL отдают консистентную копию только через свои инструменты: pg_dump, pg_dumpall, mysqldump. Копировать файлы данных «на живую» нельзя — получите несогласованный набор страниц.
Образ виртуальной машины. tar или dd диска отдают один длинный поток, который удобно сразу отправлять в хранилище, не занимая десятки гигабайт на локальном диске.
Выгрузка из внешнего сервиса. API отдаёт архив потоком, и промежуточный файл на сервере не нужен.
Во всех трёх случаях важна одна вещь: бэкап должен создаваться целиком или не создаваться вовсе. И вот здесь начинаются различия между «просто пайп» и «правильный пайп».
Правило 1. Один репозиторий — одна сущность
Соблазн сделать s3://bucket/backups и складывать туда всё: базы, конфиги, дампы ВМ. Не надо.
Причины, по которым монолитный репозиторий хуже:
- Дедупликация не работает. Restic ищет повторы внутри своего хранилища. Данные двух разных баз не пересекаются, прирост нулевой, а индекс растёт.
- Блокировки на уровне репозитория. Пока идёт
pruneв одном репозитории, параллельные операции в нём ждут. С раздельными репозиториями базы бэкапятся независимо. - Порча метаданных убивает всё. Повреждён индекс одного репозитория — недоступны все копии внутри. Раздельные репозитории ограничивают ущерб одной базой.
- Свой пароль на каждую сущность. Утечка пароля от репозитория клиентской базы не открывает остальные.
- Понятная ретенция. Для базы, где данные меняются каждый час, и для конфигов, которые меняются раз в квартал, разумные сроки хранения разные.
Практика: s3://bucket/pg/<dbname>/ — на каждую базу свой префикс и свой репозиторий. Для небольших баз это выглядит избыточно, но окупается в первый же день, когда нужно восстановить одну базу, а не разбираться в общем хранилище.
Правило 2. Сжатие — внутри restic, а не через gzip
Классический пайп «pg_dump | gzip | restic» ломает дедупликацию: gzip не даёт стабильного результата, и при малейшем изменении исходных данных весь поток выглядит новым. Restic сохраняет чанки по содержимому, но если содержимое каждый раз разное из-за сжатия, дедуп не срабатывает.
С версии 0.14 restic сжимает сам, и для новых репозиториев сжатие включено по умолчанию. Нужен только правильный формат репозитория:
restic init -r s3:s3.example.ru/backups/pg/appdb --repository-version 2
Дальше restic backup сжимает автоматически. Управлять можно флагом --compression:
auto— по умолчанию, разумный баланс;max— если объём хранилища дороже процессорного времени (дампы текстовые, сжимаются хорошо);off— если данные уже сжаты (архивы, изображения) и тратить CPU бессмысленно.
Важно: проверить, что репозиторий действительно второй версии. У старых репозиториев (созданных до 0.14) сжатия нет, и --compression для них не работает без миграции (restic migrate upgrade_repo_v2).
Правило 3. Убедиться, что бэкап полный, а не обрезанный
Это главная ловушка, и здесь с 2024 года есть правильное решение, которого не было в старых инструкциях.
Проблема: pg_dump | restic backup --stdin — restic не знает, чем закончилась команда слева от пайпа. Если pg_dump упал на середине (кончилось место, разорвалось соединение, база в recovery), restic спокойно сохранит то, что успело прийти, и снапшот будет выглядеть успешным. Восстановите вы его только в момент аварии.
Способ А (современный): --stdin-from-command
Restic сам запускает команду и проверяет её код возврата. Ненулевой код — бэкап считается неуспешным:
restic -r s3:s3.example.ru/backups/pg/appdb backup \
--stdin-from-command \
--stdin-filename appdb.dump \
-- pg_dump -Fc -d appdb
Обратите внимание на -- перед командой: всё после него restic передаёт как аргументы, не пытаясь interpretировать как свои флаги. Это --stdin-from-command из 0.17.0; проверено на 0.19.1.
Плюс схемы: если pg_dump завершился с ошибкой, снапшота не будет вообще. Это лучше, чем «есть, но битый».
Способ Б (страховка и старые версии): теги
Если версия restic старше 0.17 или нужна дополнительная защита от «команда вернула 0, но данные подозрительные», работает схема с временным тегом:
set -euo pipefail
JOB_ID="job-$(date +%s)-$$"
pg_dump -Fc -d appdb | restic -r "$REPO" backup --stdin --stdin-filename appdb.dump --tag "$JOB_ID"
# помечаем снапшот как полный
restic -r "$REPO" tag --add completed --remove "$JOB_ID"
# всё, что без метки completed, удаляем при очистке
restic -r "$REPO" forget --keep-tag completed --prune
Ключевые детали:
set -o pipefail— иначе код возврата пайпа будет равен коду restic, а падениеpg_dumpпотеряется;set -e— скрипт остановится на первой ошибке, не пытаясь «доиграть» очистку;- тег
completedставится после успешного бэкапа, аforget --keep-tag completedгарантирует, что недоделанные снапшоты не выживут.
Оба способа можно совместить: --stdin-from-command как основная защита, тег completed как маркер для ретенции и аудита.
Правило 4. Не восстанавливать напрямую в stdout
Схема restic dump latest appdb.dump | pg_restore -d appdb выглядит красиво и на практике мучительно медленная: dump отдаёт поток однопоточно, без распараллеливания. На базе в несколько гигабайт разница с восстановлением в каталог — десятки раз.
Правильно:
mkdir -p /var/restore/appdb
restic -r "$REPO" restore latest --target /var/restore/appdb
pg_restore -d appdb_new -j 4 --clean --if-exists /var/restore/appdb/appdb.dump
-j 4 даёт параллельное восстановление, --clean --if-exists — предсказуемое поведение при повторном прогоне. Для больших баз это ещё и возможность сначала восстановить в отдельную базу, проверить и только потом переключить приложение.
Готовый скрипт для набора баз
Ниже — версия под обычный сервер (systemd timer), без Kubernetes. Бэкапит все пользовательские базы по очереди, каждую в свой репозиторий.
#!/bin/bash
set -euo pipefail
REPO_ROOT="s3:s3.example.ru/backups/pg"
KEEP="--keep-last=7 --keep-daily=14 --keep-weekly=8"
EXCLUDE="postgres|template0|template1"
log() { echo "[$(date '+%F %T')] $*"; }
# глобальные объекты (роли, табличные пространства) — отдельным снапшотом
pg_dumpall --globals-only | restic -r "$REPO_ROOT/globals" backup \
--stdin --stdin-filename globals.sql --tag completed
DB_LIST=$(psql -Atq -c "SELECT datname FROM pg_database WHERE datistemplate = false;" | grep -vE "^($EXCLUDE)$")
for db in $DB_LIST; do
REPO="$REPO_ROOT/$db"
log "бэкап $db"
restic -r "$REPO" cat config >/dev/null 2>&1 || \
restic -r "$REPO" init --repository-version 2
restic -r "$REPO" backup \
--stdin-from-command --stdin-filename "$db.dump" --tag completed \
-- pg_dump -Fc -d "$db"
restic -r "$REPO" forget $KEEP --prune
done
log "готово"
Что здесь сделано намеренно:
globalsотдельно.pg_dumpне сохраняет роли и права — при восстановлении на новый кластер без них база поднимется, но приложение не сможет подключиться.restic cat configвместоinitнапрямую. Проверка «репозиторий уже есть» без лишних ошибок в логах.forget --pruneодной командой. В старых версиях это были две операции, иpruneбезforgetне удалял снапшоты, а только данные.- Никакого
unlock --remove-all. О нём ниже — это самый опасный флаг в этой истории.
Запуск через systemd:
# /etc/systemd/system/pg-backup.service
[Unit]
Description=PostgreSQL backup to S3 via restic
[Service]
Type=oneshot
User=postgres
EnvironmentFile=/etc/pg-backup.env
ExecStart=/usr/local/bin/pg-backup.sh
# /etc/systemd/system/pg-backup.timer
[Unit]
Description=Ночной бэкап PostgreSQL
[Timer]
OnCalendar=*-*-* 02:30:00
Persistent=true
[Install]
WantedBy=timers.target
Persistent=true важен: если сервер был выключен в 02:30, бэкап выполнится сразу после загрузки, а не будет ждать следующих суток.
В /etc/pg-backup.env — RESTIC_PASSWORD, ключи S3 и параметры подключения. Права 0600, владелец — пользователь сервиса. Restic читает RESTIC_PASSWORD, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_DEFAULT_REGION из окружения, поэтому пароль не нужно передавать в командной строке (где он попадает в ps и историю).
Retention: сколько хранить и почему это дороже, чем кажется
Типовая стратегия для базы, которая меняется постоянно:
--keep-last=7 --keep-daily=14 --keep-weekly=8 --keep-monthly=6
Читается так: семь последних снапшотов, по одному в день за две недели, по одному в неделю за два месяца, по одному в месяц за полгода. Restic сам выберет, какие снапшоты попадают под правила, и удалит остальные.
Что нужно понимать про деньги: дедупликация работает внутри одного репозитория, поэтому стоимость растёт не как «размер базы × число снапшотов». Но полностью бесплатной история не бывает: чем чаще меняются данные и чем длиннее retention, тем больше уникальных чанков.
Практический ориентир: сжатый дамп базы 5 ГБ при ежедневном бэкапе и retention «2 недели дневных + 2 месяца недельных» обычно занимает в хранилище 15–40 ГБ. Если цифра кажется большой — это не restic плохой, это реальный объём истории. Уменьшать надо retention, а не отключать бэкапы.
Отдельно: prune — операция тяжёлая. Она читает индекс и переписывает данные, на больших репозиториях может идти часами и требует эксклюзивной блокировки. Поэтому prune не стоит делать после каждого бэкапа на больших объёмах: разумно раз в неделю отдельной задачей, а ежедневный forget — без --prune.
Проверка целостности: раз в месяц, а не «когда-нибудь»
# быстрая проверка структуры (метаданные, индекс)
restic -r "$REPO" check
# выборочная проверка самих данных: 5% чанков
restic -r "$REPO" check --read-data-subset=5%
check без флага проверяет только структуру и не читает содержимое. Полная проверка (--read-data) читает весь репозиторий — на больших объёмах это дорого по трафику, поэтому практичная схема: раз в месяц выборочно 5–10% данных, раз в квартал — полная проверка критичного репозитория.
И отдельно от проверки целостности: восстановление. Бэкап, из которого ни разу не восстанавливались, — это не бэкап, а надежда. Раз в месяц разворачивайте дамп на тестовый сервер, поднимайте базу и выполняйте пару запросов из приложения. Записывайте время восстановления — это ваш реальный RTO, а не предполагаемый.
Мониторинг: что должно вас разбудить
Три сигнала, которые покрывают почти все сценарии:
- Возраст последнего снапшота. Простейший чекер:
restic snapshots --json | jqи сравнение с текущим временем. Нет снапшота за сутки — алерт. Это ловит и упавший таймер, и «команда падает, а мы не смотрим». - Код возврата последней задачи.
systemctl status pg-backup.serviceили журнал:Failedдолжен уходить в мониторинг, а не оставаться в логах. - Свободное место в бакете / рост объёма. Резкий рост — либо всплеск данных, либо retention не работает (например,
forgetпадает из-за блокировки).
Отдельно: если сервер, на котором крутится бэкап, сам умер, вы об этом узнаете только по отсутствию снапшотов. Поэтому проверку «есть ли свежий снапшот» лучше делать снаружи — с другой машины, у которой есть только право читать репозиторий.
Опасный флаг: unlock --remove-all
В интернете много скриптов, где перед бэкапом стоит:
restic unlock --remove-all
Логика авторов понятна: «чтобы не мешали старые блокировки». Проблема в том, что этот флаг снимает блокировки принудительно, не разбираясь, кто их держит. Если в этот момент идёт другой бэкап или prune, вы получите повреждённый репозиторий — ровно тот сценарий, от которого restic защищался блокировками.
Правило: unlock --remove-all допустим только там, где платформа гарантирует, что другого процесса нет (например, Kubernetes CronJob с concurrencyPolicy: Forbid, или единственный systemd-таймер с Type=oneshot). Во всех остальных случаях — обычный unlock, который снимает только просроченные блокировки.
PostgreSQL: когда дамп — не лучший выбор
Дамп логичен до определённого размера. Дальше становятся заметны две вещи: pg_dump читает всю базу и нагружает её, а восстановление занимает часы, потому что это последовательное применение SQL.
Если база большая и важна точка восстановления, правильнее физический бэкап и WAL:
pg_basebackup— полная физическая копия кластера;- архивирование WAL (
archive_commandилиpg_receivewal) — возможность восстановиться на любую минуту; - PITR — откат на момент до инцидента, а не «на прошлую ночь».
Схема «dump + restic» остаётся удобной для небольших баз и как дополнительный слой: дамп переживает порчу формата данных и читается почти любым инструментом. Для больших инсталляций разумно держать оба слоя.
Про SQLite и репликацию вместо дампа у нас есть отдельный разбор — Litestream на VPS: репликация SQLite в S3. Логика та же: непрерывная копия плюс проверенное восстановление.
Частые ошибки
Бэкап есть, но пустой. pg_dump падал, restic сохранил обрезанный поток. Лечится --stdin-from-command или схемой с тегами.
Восстановление идёт часами. Используется restic dump в пайп. Восстанавливайте в каталог, затем pg_restore -j.
Снапшоты перестали появляться. Чаще всего: истёк срок ключа S3, кончилось место в бакете, или таймер не установлен (systemctl enable забыли). Проверять надо код возврата, а не «вроде вчера работало».
repository is already locked. Параллельный запуск: таймер сработал, пока предыдущий бэкап ещё шёл. Лечится Type=oneshot + Persistent=true и запретом параллельных запусков, а не снятием блокировок.
wrong password or no key found. Потерян RESTIC_PASSWORD или ключ не в том окружении. Пароль надо хранить отдельно от сервера: если сервер и пароль погибли вместе, бэкапа нет.
Восстановили базу, а приложение не подключается. Не восстановлены глобальные объекты (роли, права). Поэтому pg_dumpall --globals-only в отдельном репозитории.
Данные в бакете растут быстрее базы. Работает forget без prune: снапшоты удалены из индекса, но данные лежат. Нужен периодический prune.
Чек-лист настройки
- Отдельный бакет или префикс под каждую базу, отдельный пароль репозитория.
- Репозиторий версии 2 — сжатие внутри restic, без
gzipв пайпе. - Бэкап через
--stdin-from-command(или тегиcompleted, если версия старая). - Глобальные объекты PostgreSQL — отдельным снапшотом.
- Retention определён цифрами,
prune— по расписанию, не после каждого бэкапа. check --read-data-subsetраз в месяц, полная проверка раз в квартал.- Тестовое восстановление раз в месяц с замером времени.
- Внешняя проверка «возраст последнего снапшота» с алертом в мессенджер.
- Пароль и ключи — в
EnvironmentFileс правами 0600, не в командной строке. unlock --remove-all— только при гарантии единственного процесса.
Какой сервер нужен под такую схему
Требования скромные, но два пункта важны.
- Диск NVMe.
pg_dumpчитает всю базу и пишет поток; на медленном диске это заметно удлиняет окно бэкапа и нагружает базу. - Процессор для сжатия.
--compression max— это CPU. На одном ядре сжатие большого дампа может занять больше времени, чем сам дамп. - Стабильная сеть. Обрыв соединения с S3 прерывает загрузку чанков; restic продолжит с места обрыва, но частые обрывы превращают бэкап в бесконечный.
- Место под временное восстановление. Учения по restore требуют места под дамп плюс распакованную базу.
Для базы до 20–30 ГБ обычно достаточно 2 ядер, 4 ГБ памяти и NVMe. Если планируете физические бэкапы и WAL — считайте отдельно: WAL растёт быстро при интенсивной записи.
Посмотреть, что есть по конфигурациям, можно на странице наших тарифов — на 04.11.2026 конфигурации начинаются от 299 ₽ в месяц. Смежное по теме: как делают бэкапы на VPS, первый вход по SSH, базовая безопасность сервера, почему VPS тормозит, как выбрать тариф и что делать, если VPS не работает.
Частые вопросы
Чем --stdin-from-command лучше обычного --stdin?
Тем, что restic сам запускает команду и проверяет её код возврата. Если pg_dump упал, снапшот не создаётся. С --stdin restic видит только поток и не знает, что он оборван.
Можно ли бэкапить несколько баз в один репозиторий?
Технически да, но не стоит: теряется смысл дедупликации, блокировки становятся общими, а порча метаданных затрагивает все базы сразу. Один репозиторий на базу проще восстанавливать и удалять.
Нужно ли сжимать дамп через gzip перед restic?
Нет. Репозиторий версии 2 сжимает сам, а gzip в пайпе ломает дедупликацию: при малейшем изменении данных сжатый поток выглядит полностью новым.
Почему restic dump такой медленный?
Он отдаёт данные однопоточно, без распараллеливания. Для восстановления базы используйте restore в каталог и pg_restore -j, это в разы быстрее.
Сколько места займёт история в S3?
Зависит от скорости изменения данных и retention. Ориентир: сжатый дамп 5 ГБ при ежедневных бэкапах за два месяца обычно занимает 15–40 ГБ. Точнее покажет restic stats.
Можно ли восстановиться на конкретную дату?
Да, если снапшоты за эту дату не удалены политикой retention. restic snapshots покажет список, restore <snapshot-id> восстановит нужный. Промежутков внутри дня у дампов нет — если нужна точность до минуты, это уже PITR через WAL.
Безопасно ли хранить пароль репозитория на том же сервере?
Хранить надо, но понимать риск: если сервер и пароль потеряны вместе, бэкап не расшифровать. Пароль (или ключ от менеджера секретов) держите вне сервера — в хранилище секретов, у второго администратора.
Что делать, если prune идёт слишком долго?
Это нормально для больших репозиториев: он читает индекс и переписывает данные. Не запускайте его после каждого бэкапа, вынесите в недельную задачу и не прерывайте — прерывание оставит репозиторий в промежуточном состоянии (он восстановится, но следующий prune будет дольше).
Итог
Схема «restic + stdin» даёт то, что нужно от бэкапа базы: шифрование на клиенте, дедупликацию, хранение вне сервера и историю, из которой можно выбрать точку восстановления. Работает она надёжно при четырёх условиях: репозиторий на каждую сущность, сжатие внутри restic, проверка полноты через --stdin-from-command и регулярное восстановление, а не только check.
Остальное — дисциплина: retention цифрами, prune по расписанию, пароль отдельно от сервера и алерт на «снапшотов нет сутки». Эти пять привычек отличают бэкап, который спасает, от бэкапа, который просто занимает место в облаке.