GitLab Backup and Recovery: S3, HA, Automation
Six months ago, a client lost access to their project after a RAID array failure. GitLab performed daily backups, but during restoration it turned out that /etc/gitlab/gitlab-secrets.json was not saved separately. As a result, all CI/CD variables, webhook tokens, and 2FA secrets became inaccessible. Recovery took two days instead of the planned hour. We integrated a full backup system covering the database, configuration files, and object storage. About a third of GitLab recovery incidents are related to missing secrets.json — the average downtime is 8 hours. Our methodology reduces MTTR to 15 minutes, eight times faster than the standard approach. Here’s how to configure GitLab backup and recovery without surprises and with guarantees.
Which GitLab components does the built-in tool not back up?
The built-in rake task gitlab-backup create is a good starting point, but it doesn't cover everything. Its responsibility includes:
- PostgreSQL (full dump via pg_dump)
- Git repositories (via Gitaly)
- Uploaded files (avatars, attachments, LFS objects when stored locally)
- CI/CD artifacts (if stored locally)
- Container Registry (if local)
- Encryption keys (via a separate mechanism, but not secrets.json)
But it does not automatically back up:
-
/etc/gitlab/gitlab.rband/etc/gitlab/gitlab-secrets.json— critical - TLS certificates
- Custom nginx
- Consul/Patroni data in HA setups
gitlab-secrets.json contains keys for encrypting 2FA secrets, webhook tokens, and CI/CD variables. If the database is restored without the corresponding gitlab-secrets.json, all encrypted data becomes inaccessible: users lose 2FA, integrations stop working. This is a typical mistake that leads to partial data loss in 30% of projects.
How to configure backup for Omnibus installations?
Configuration starts with the config file. Example for Omnibus:
# /etc/gitlab/gitlab.rb
gitlab_rails['backup_path'] = '/mnt/backups/gitlab'
gitlab_rails['backup_keep_time'] = 604800
gitlab_rails['backup_upload_connection'] = {
'provider' => 'AWS',
'region' => 'eu-central-1',
'aws_access_key_id' => 'AKIA...',
'aws_secret_access_key' => 'secret'
}
gitlab_rails['backup_upload_remote_directory'] = 'gitlab-backups-prod'
gitlab_rails['backup_archive_permissions'] = 0644
After updating the configuration:
gitlab-ctl reconfigure gitlab-backup create STRATEGY=copy STRATEGY=copy creates copies of files instead of moving them. This is important for repositories that continue receiving pushes during backup. Without this option, actively written data may be partially captured.
For large installations, you can skip unnecessary components, e.g., only the database or only the repositories. But a full backup is standard for production. We recommend a full backup daily, and configuration backup on every change. Step-by-step instructions:
- Configure
gitlab.rbas above. - Run
gitlab-ctl reconfigure. - Run
gitlab-backup create STRATEGY=copy. - Verify the archive in S3 or a local directory.
- Set up cron or systemd timer for automation.
Why back up configuration separately?
Configuration files cannot be captured by built-in tools — a separate script is needed. We use this:
#!/bin/bash
CONF_BACKUP=/mnt/backups/gitlab-config/$(date +%Y%m%d)
mkdir -p "$CONF_BACKUP"
cp /etc/gitlab/gitlab.rb "$CONF_BACKUP/"
cp /etc/gitlab/gitlab-secrets.json "$CONF_BACKUP/"
tar czf - "$CONF_BACKUP" | gpg --symmetric --cipher-algo AES256 -o "$CONF_BACKUP.tar.gz.gpg"
aws s3 cp "$CONF_BACKUP.tar.gz.gpg" s3://gitlab-config-backups/
rm -rf "$CONF_BACKUP" "$CONF_BACKUP.tar.gz.gpg"The script encrypts the archive with a GPG key and sends it to a separate S3 bucket with versioning. Store the GPG key in a password manager — without it, recovery is impossible.
How to restore from a backup?
Restoration is only possible to the same GitLab version. Both major and minor versions must match. Procedure:
# Stop processes that write to the database
gitlab-ctl stop puma
gitlab-ctl stop sidekiq
# Ensure the database is available
gitlab-ctl start postgresql
gitlab-ctl start gitaly
# Restore (filename without _gitlab_backup.tar)
gitlab-backup restore BACKUP=20250325_17.8.1After restoration, copy secrets.json and run gitlab-ctl reconfigure. Then run verification:
gitlab-ctl start gitlab-rake gitlab:check SANITIZE=true gitlab:check will show if anything restored incorrectly: wrong file permissions, issues connecting to Redis or PostgreSQL.
Typical errors and their solutions
Version mismatch is the most common error. Restoring the database to a different version results in GitLab version mismatch. Solution: first install the backup version, restore, then upgrade. The second most common is insufficient disk space. The backup creates a local archive, so /var/opt/gitlab/backups needs a separate volume. Third: LFS objects stored on external S3 will not be included in the backup — they must be copied separately using rclone or aws s3 sync. And most importantly: losing gitlab-secrets.json makes the restoration incomplete — all encrypted data becomes unavailable.
| Strategy | Recovery Time | Data Loss Risk | Storage Cost |
|---|---|---|---|
| Only local backup | 1–2 hours | High (disk failure) | Low |
| Local + S3 | 15–30 minutes | Low (geo-redundancy) | Medium |
| HA with synchronous replication | <5 minutes | Minimal | High |
What is included in a turnkey backup setup?
We provide the full cycle:
- audit of the current GitLab infrastructure
- backup configuration (DB, repositories, config, S3)
- encryption of secrets.json and GPG setup
- automation via systemd timer or cron
- integration with GitLab CI/CD for restore testing
- runbook documentation and team training
Estimated timelines:
| GitLab Scale | Setup Time |
|---|---|
| Single node, up to 50 GB | 1 day |
| Single node with object storage | 1–2 days |
| HA (Gitaly cluster, Patroni) | 3–5 days |
Cost is calculated individually after an audit. Contact us for a project assessment. Our experience: 7+ years of GitLab in production, over 30 successful migration and backup projects. Order backup setup today and get guaranteed recovery in 15 minutes. For more details on the recovery procedure, see official GitLab documentation.







