Everything your gateway knows (messages, contacts, conversations, campaigns, rules, settings, accounts, paired phones) lives on your machine, and nowhere else: we keep no copy. Losing the disk without a backup means losing all of it. This page explains how to protect yourself.
Automatic backup is on by default: every day at 03:00 in the platform time zone, seven copies kept, on new installations and on installations updated from an earlier version alike. Check it, and change it, in Settings > Backup and export (see Automatic backup). These copies stay on the machine they protect: on the day you install, also decide how you keep copies off the machine.
The three tools, and what each is for
| Tool | What it produces | Contains the master key | Interrupts sending | Best for |
|---|---|---|---|---|
sms-gateway backup (command line) |
An archive of the whole data folder (.tar.gz) |
Yes | A few seconds | Disaster recovery, moving to another machine |
| Automatic backup and Back up now (dashboard or API) | A copy of the database (.db), kept on the machine |
No | No | Daily restore points, a copy before a risky change, restoring from the dashboard |
| Full export (dashboard or API) | The database, or messages, contacts and conversations as CSV or JSON | No | No | Taking your data to another tool, spreadsheets, archiving |
The master key (secret.key) protects the secrets of your paired phones, the webhook secrets
and your stored license key. A database restored without its master key opens normally, but:
- every phone must be paired again;
- every webhook secret, and the secret of every rule action that calls a URL, must be set again (and updated on the receiving side);
- the license key must be entered again.
That is why the sms-gateway backup archive, which contains the key, is the one to keep for a
disaster. The key is kept out of the database on purpose: a database copy that leaks does not hand
over your phones.
Who can do what
| Action | Dashboard | API key scope |
|---|---|---|
| See, create, download, restore backups | Superadministrator only | admin |
| Export the full database | Superadministrator only | admin |
| Export messages, contacts, conversations | Every role | admin |
sms-gateway backup |
- | root on the machine |
Backups and the database export contain the accounts of the dashboard, which is why they are reserved to the superadministrator. Other roles see only the Full export part of the screen.
Make a backup
From the dashboard
Settings > Backup and export > Back up now. The gateway writes a consistent copy of the database without interrupting sending, reads it back to make sure it is valid, and only then counts it as successful. The new copy appears in Available backups, and the attempt in Backup log.
If the copy fails (most often: the disk is full), the attempt shows as Failed in Backup log with a short reason, and the damaged file is deleted.
From the command line
On the machine, from the folder where you want the archive:
sudo sms-gateway backup
| Point | Behavior |
|---|---|
| What is copied | The whole data folder: database, master key, and the backups made from the dashboard |
| Where it is written | The current folder, as sms-gateway-backup-<YYYYMMDD-HHMMSS>.tar.gz, readable by root only |
| Interruption | The gateway stops for the time of the copy (seconds for a normal database), then restarts; the command returns once the gateway answers again. Phones keep their messages meanwhile |
| Options | None |
| Old archives | Never deleted by the command: clean up yourself |
On Windows, there is no sms-gateway command. From %ProgramData%\SmsGateway, in PowerShell:
docker compose stop gateway
docker run --rm -v sms-gateway_gateway-data:/data:ro -v "${PWD}:/out" alpine:3 tar czf /out/sms-gateway-backup.tar.gz -C /data .
docker compose start gateway
Never copy the database file by hand while the gateway runs. A copy taken in the middle of a write gives a damaged file, and you only find out on the day you need it. Use one of the tools above.
Automatic backup
The gateway backs up its database on its own, without interrupting sending, and reads every copy back before counting it as successful. The copies appear in Available backups with the origin Scheduled, and every attempt in Backup log.
Set it in Settings > Backup and export, Scheduled backup card (Superadmin only), then Save. The change applies within a minute, without a restart.
| Setting | Default | Possible values | Effect |
|---|---|---|---|
| Automatic backup | On | On, off | Off: only the backups you make yourself exist. Back up now works either way |
| Frequency | Once a day | Once a day, every N hours | How the backups are spaced |
| Time | 03:00 | Any time, HH:MM | Once a day: when the backup runs, in the platform time zone (Settings > Sending) |
| Every (hours) | 24 | 1 to 168 (one week) | Every N hours: hours between two automatic backups |
| Copies kept | 7 | 1 to 365 | See How many dashboard backups are kept |
Good to know:
- Daylight saving time is followed: 03:00 stays 03:00 local time on both sides of the change. A time that does not exist on the day of the change (inside the skipped hour) runs once, just after it; a time that occurs twice runs once.
- A gateway that was stopped at the due time makes the missed backup once when it starts again, not one per missed day. A fresh installation, or one that never made an automatic backup, makes its first one within a minute.
- A manual backup does not move the schedule: the nightly backup still runs after a Back up now at 02:59.
- One backup at a time: an automatic backup that falls while another backup is being written waits for the next minute.
- Every N hours counts from the previous automatic backup, failed or not: a full disk is retried once per interval, not every minute.
The Next backup box shows when the next one is due, in the platform time zone: None when the automatic backup is off, On the next pass when it is due within the minute.
Through the API, with an API key of scope admin: PATCH /api/v1/settings with a backup block
(enabled, frequency daily or interval, dailyAt HH:MM, intervalHours, retention). An
omitted field is left as it is.
curl -X PATCH https://sms.example.com/api/v1/settings \
-H "X-API-KEY: sk_..." -H "Content-Type: application/json" \
-d '{"backup": {"frequency": "daily", "dailyAt": "02:30", "retention": 14}}'
The command-line archive is not scheduled by the gateway. Run sudo sms-gateway backup when you
want the full archive with the master key: after installing, before an update or a move, and
regularly if you keep it off the machine.
Keep copies off the machine
A backup that stays on the disk it protects does not survive the loss of that disk, and one that stays in the same building does not survive a fire or a theft.
- Download a dashboard backup (automatic or manual): in Available backups, the download
button next to each file.
Through the API:
GET /api/v1/backups/{name}/download. - Copy the
sms-gateway backuparchives to another server or to storage of your choice (scp,rsync, your provider's object storage).
Three rules:
- Encrypt the copies. Backups are not encrypted: they contain every message, every contact and every phone number in clear, and the command-line archive also contains the master key.
- Keep the master key, or the full archive that contains it, even if you rely on dashboard backups.
- Delete old copies you no longer need, on both sides.
How many dashboard backups are kept
The Copies kept value of the Scheduled backup card (7 by default, from 1 to 365) applies to Available backups, automatic and manual copies together. After each successful backup, the oldest copies beyond that number are deleted. Never before, and never after a failure: a failed backup never costs you an older good one. Lowering the value deletes the surplus at the next successful backup, not at once.
Disk space: each copy is about the size of the database. Seven copies of a database of 200 MB take about 1.4 GB in the data folder.
A file you add yourself counts too. Any
.dbfile placed in the backups folder is listed as restorable and counts towards the copies kept. Keep your own original elsewhere: do not rely on that folder as your only copy.
Restore a dashboard backup
A restore replaces the whole database with the chosen copy. Everything written since that copy (messages, contacts, settings, accounts) disappears from the database in use.
- Settings > Backup and export > Available backups, the restore button on the chosen line.
- Read the confirmation, tick I understand that the data written since this backup will be lost, then Prepare the restore. The gateway checks the file and prepares it; nothing is replaced yet.
- A Restore pending banner appears, with Cancel the restore if it is not the right copy. Preparing another copy replaces the pending one.
- Restart the gateway to apply it:
sudo sms-gateway restart(on Windows,docker compose restart gatewayfrom the installation folder). - Sign in again, and check your data.
Why a restart: the database cannot be swapped while the gateway uses it. The swap happens at the next start, before anything else, and only if the prepared file is exactly the one that was checked.
The gateway refuses a copy in two cases, each with a precise message:
| Refusal | Meaning | What to do |
|---|---|---|
BACKUP_UNREADABLE |
The file is damaged, or is not a backup of this product | Take another copy |
BACKUP_SCHEMA_TOO_RECENT |
The copy was made by a newer version of the gateway | Update the gateway (sms-gateway update), then try again |
A copy made by an older version is accepted: it is brought up to date automatically at the restart.
A failed restore never prevents the gateway from starting: it starts on the database it had, and
the logs (sms-gateway logs) say why.
The previous database is kept
The database that was in use is not deleted: it is set aside in the data folder as
gateway.db.pre-restore-<timestamp>. It is your way back if the restored state does not suit you,
and it is never deleted automatically: remove it yourself once you are satisfied, especially on a
large database, or it will eventually fill the disk.
To go back to it, with the gateway stopped:
sudo sms-gateway stop
# Find the exact name of the set-aside file
docker run --rm -v sms-gateway_gateway-data:/data alpine:3 ls -la /data
# Put the previous database back, keeping the restored one aside
docker run --rm -v sms-gateway_gateway-data:/data alpine:3 sh -c '
mv /data/gateway.db /data/gateway.db.restore-rejected &&
rm -f /data/gateway.db-wal /data/gateway.db-shm &&
mv /data/gateway.db.pre-restore-1756800000 /data/gateway.db'
sudo sms-gateway start
Replace 1756800000 with the value shown by ls. The -wal and -shm files belong to the database
being set aside: they must be removed, otherwise they would damage the one you put back.
Restore a copy made on another machine
A backup downloaded earlier, or taken from another installation, can be restored the same way once it is in the backups folder of this gateway:
# Click "Back up now" once first, so that the backups folder exists
docker run --rm -v sms-gateway_gateway-data:/data -v "$PWD:/in" alpine:3 sh -c '
cp /in/gateway-20260901T020000Z-01930f2c.db /data/backups/ &&
chown 65532:65532 /data/backups/gateway-20260901T020000Z-01930f2c.db'
The file appears in Available backups with the origin Imported. Its name must end in .db,
start with a letter or a digit, and contain only letters, digits, dots, dashes and underscores. Then
follow Restore a dashboard backup.
Remember: this copy does not carry the master key of its original machine. If this gateway does not
have the same secret.key, phones must be paired again and the license key entered again.
Restore a full archive (sms-gateway backup)
This brings back the whole installation as it was, master key included: phones reconnect without pairing, and the license needs nothing. It replaces everything currently in the data folder.
sudo sms-gateway stop
docker run --rm -v sms-gateway_gateway-data:/data -v "$PWD:/in" alpine:3 sh -c '
rm -rf /data/* &&
tar xzf /in/sms-gateway-backup-20260901-023000.tar.gz -C /data'
sudo sms-gateway start
The gateway must be at least the version that made the archive: update it first if needed. On a new machine, install the gateway first, then run these commands (see Migrating to another server).
Export your data
Settings > Backup and export > Full export. Exports are not backups: they let you take your data elsewhere, in open formats that any tool reads.
| Export | Content | Formats | API |
|---|---|---|---|
| Full database | A fresh copy of the whole database, accounts included, opened by any SQLite tool | SQLite | GET /api/v1/export/database |
| Messages | The whole history, texts included | CSV (default), JSON | GET /api/v1/export/messages?format=csv |
| Contacts | The directory as stored, with labels and opt-outs | CSV (default), JSON | GET /api/v1/export/contacts?format=json |
| Conversations | The threads, without the texts (they are in the messages export) | CSV (default), JSON | GET /api/v1/export/conversations |
The columns, in order:
- Messages:
id,conversationId,direction,phone,body,encoding,segments,status,errorCode,deviceId,simSlot,attempts,createdAt,sentAt,deliveredAt,failedAt - Contacts:
id,phone,firstName,lastName,company,email,notes,tags,blacklisted,createdAt,updatedAt - Conversations:
id,phone,contactId,state,unreadCount,pinned,archived,assignedTo,createdAt,lastMessageAt
Good to know:
- Rows go from the oldest to the newest. An empty export still has its header line (CSV) or
[](JSON). A missing date is an empty field, never a fake date. - These files contain message texts and full phone numbers: store them accordingly.
- Exports stay available even if your license is withdrawn: your data remains yours.
- The full database export needs temporary space on the machine, about the size of the database. If it fails on a very large database, make a backup and download it instead.
Be warned when backups fail
A gateway that has stopped backing up looks exactly like one that works. Two ways to find out:
- Backup log in Settings > Backup and export lists every attempt, failures included, with a short reason (for example snapshot could not be written: most often a full disk). A failure is also written, as an error, in the Activity log.
- Notifications: the Backup failed alert (Settings > Notifications) is on by default and sends a message on every channel you configured (SMS, e-mail, browser) as soon as an automatic or manual backup fails. See Settings, notifications.
- Webhooks: subscribe a webhook to the
backup.failedevent (andbackup.completedif you want a positive signal) to be alerted by your own tools. See Webhooks.
Check that a backup is worth something
A backup nobody ever restored is a hope, not a backup. The gateway already reads back every copy it makes, which proves the file is a valid database. It does not prove that you know how to bring it back.
In one minute, on a downloaded copy, with the sqlite3 tool:
sqlite3 gateway-20260901T020000Z-01930f2c.db 'PRAGMA quick_check;'
# must answer: ok
sqlite3 gateway-20260901T020000Z-01930f2c.db 'SELECT count(*) FROM messages; SELECT count(*) FROM contacts;'
# plausible numbers
The real test: restore a full archive on a spare machine (a small VPS, a virtual machine), following Restore a full archive, sign in with your usual account and check that your conversations are there. Do it on a machine that phones do not know: paired phones only connect to the address of your real gateway, so the test copy does not disturb them. Delete the test machine afterwards: it contains all your data.
Do this test after each important update, after any change to your backup routine, and at least once before you need it.
API reference
All these operations need an API key of scope admin (header X-API-KEY) or a superadministrator
session. Details in the API reference.
| Operation | Endpoint | Answer |
|---|---|---|
| Make a backup now | POST /api/v1/backups |
201 with the attempt, 503 if it failed |
| List available backups | GET /api/v1/backups |
Newest first; journalled: false for an imported file |
| Backup log | GET /api/v1/backups/attempts |
Every attempt, failures included |
| Schedule in effect | GET /api/v1/backups/schedule |
enabled, frequency, dailyAt, timezone, intervalSeconds, retention, lastAttemptAt, nextRunAt |
| Change the schedule | PATCH /api/v1/settings with a backup block |
The settings in force, backup included; 422 on a value out of range |
| Download a backup | GET /api/v1/backups/{name}/download |
The .db file |
| Prepare a restore | POST /api/v1/backups/{name}/restore |
202 with restartRequired: true; 404, or 422 with BACKUP_UNREADABLE / BACKUP_SCHEMA_TOO_RECENT |
| See the pending restore | GET /api/v1/backups/restore |
{"pending":true,"name":"..."} or {"pending":false} |
| Cancel the pending restore | DELETE /api/v1/backups/restore |
204 |
| Exports | GET /api/v1/export/{database,messages,contacts,conversations} |
The file |
A backup is refused while the license is withdrawn (it is a write); downloads and exports keep working.
Current limits
- No encryption and no automatic upload of backups: copying them elsewhere and encrypting them is up to you.
- No upload of a backup file from the dashboard: a copy from elsewhere is placed in the backups folder with the command shown above.
- The
gateway.db.pre-restore-...files are never deleted automatically.