Bacula is a network backup system built from separate daemons: a Director that schedules jobs and keeps a catalog database, a Storage Daemon that writes backup volumes, and a File Daemon on every machine you back up. In this tutorial you will install the open-source Bacula packages from Ubuntu 24.04 with a PostgreSQL catalog, store backups on local disk, back up the server itself and one remote client on a nightly schedule, and restore files with bconsole.
Prerequisites
To follow this guide you need:
- A server running Ubuntu 24.04 LTS for the backup server, for example a CubePath VPS, with at least 2 GB of RAM. Ideally give it a separate disk or volume for backup data, mounted at
/srv/bacula. - A second Ubuntu 24.04 server to back up as a remote client (optional, used in Step 7).
- A non-root user with
sudoprivileges on both. - A private network between them. The examples use
10.0.0.10for the backup server and10.0.0.21for the clientweb1.
Understanding the Bacula components
| Component | Package | Port | Runs on |
|---|---|---|---|
Director (bacula-dir) | bacula-director-pgsql | 9101 | Backup server |
Storage Daemon (bacula-sd) | bacula-sd | 9103 | Backup server |
File Daemon (bacula-fd) | bacula-fd | 9102 | Every machine you back up |
Console (bconsole) | bacula-console | - | Wherever you administer from |
| Catalog | PostgreSQL | 5432 (local) | Backup server |
During a backup, the Director connects to the File Daemon and tells it which Storage Daemon to use. The File Daemon then connects to the Storage Daemon directly and streams the data. Each connection is authenticated with a shared password, so the passwords in the different configuration files must match. Keep this in mind: most Bacula errors are a name or password that does not match between two files.
Step 1 - Installing PostgreSQL and Bacula
Install PostgreSQL first, so the Bacula package can create its catalog database during installation:
sudo apt update
sudo apt install postgresql
Now install the Director with PostgreSQL support, the Storage Daemon, the File Daemon and the console:
sudo apt install bacula-director-pgsql bacula-sd bacula-fd bacula-console
The installer asks Configure database for bacula-director-pgsql with dbconfig-common? Answer Yes, then press Enter to accept a randomly generated password for the database user. The package creates the bacula database and its tables for you.
Check that the catalog exists and that the three daemons are running:
sudo -u postgres psql -lqt | cut -d '|' -f 1 | grep -w bacula
systemctl is-active bacula-director bacula-sd bacula-fd
bacula
active
active
active
Open the console to confirm it can reach the Director:
echo "status director" | sudo bconsole
The output starts with Connecting to Director localhost:9101 and shows the Director version and No Jobs running.
Step 2 - Collecting names and passwords
The package generates random passwords in each configuration file. You will reuse them, so print them now.
The Director's own name, which the other daemons use to recognize it:
sudo grep -A 2 "^Director {" /etc/bacula/bacula-dir.conf
Director { # define myself
Name = backup-dir
DIRport = 9101 # where we listen for UA connections
The password the Director must use with the Storage Daemon (first Director block in bacula-sd.conf):
sudo grep -A 2 "^Director {" /etc/bacula/bacula-sd.conf
Director {
Name = backup-dir
Password = "Xk8pR2...generated..."
And the password the Director must use with the local File Daemon:
sudo grep -A 2 "^Director {" /etc/bacula/bacula-fd.conf
Note the three values. The examples below use backup-dir as the Director name, your_sd_password and your_local_fd_password. The files also contain a second Director block with Monitor = yes; ignore it.
Step 3 - Configuring disk storage
Create the directory where the Storage Daemon writes backup volumes. On Ubuntu the daemon runs as user bacula and group tape:
sudo mkdir -p /srv/bacula/volumes
sudo chown bacula:tape /srv/bacula/volumes
sudo chmod 750 /srv/bacula/volumes
Open the Storage Daemon configuration:
sudo nano /etc/bacula/bacula-sd.conf
In the Storage { ... } block at the top, look for SDAddress. Ubuntu sets it to 127.0.0.1, which only works for local clients. Remote File Daemons connect to the Storage Daemon directly, so change it to the server's private IP:
SDAddress = 10.0.0.10
Then add a new Device resource at the end of the file:
Device {
Name = LocalDisk
Media Type = LocalDisk
Archive Device = /srv/bacula/volumes
LabelMedia = yes
Random Access = yes
AutomaticMount = yes
RemovableMedia = no
AlwaysOpen = no
Maximum Concurrent Jobs = 5
}
LabelMedia = yes lets Bacula create and label new volume files automatically, so you never have to label disk volumes by hand. Test the syntax and restart the daemon:
sudo bacula-sd -t -c /etc/bacula/bacula-sd.conf
sudo systemctl restart bacula-sd
sudo ss -tlnp | grep 9103
LISTEN 0 50 10.0.0.10:9103 0.0.0.0:* users:(("bacula-sd",pid=4211,fd=3))
The -t test prints nothing when the file is valid.
Step 4 - Defining storage, pool, schedule and fileset in the Director
Rather than editing the long default Director file, keep your resources in a separate file and include it. Add this line at the very end of /etc/bacula/bacula-dir.conf:
sudo nano /etc/bacula/bacula-dir.conf
@/etc/bacula/conf.d/local.conf
Create the included file with the same ownership as the other configuration files, so the Director (running as bacula) can read it:
sudo mkdir -p /etc/bacula/conf.d
sudo install -m 640 -o root -g bacula /dev/null /etc/bacula/conf.d/local.conf
sudo nano /etc/bacula/conf.d/local.conf
Add the following resources. Replace your_sd_password and your_local_fd_password with the values from Step 2:
Storage {
Name = LocalDisk
Address = 10.0.0.10
SDPort = 9103
Password = "your_sd_password"
Device = LocalDisk
Media Type = LocalDisk
Maximum Concurrent Jobs = 5
}
Pool {
Name = DiskPool
Pool Type = Backup
Recycle = yes
AutoPrune = yes
Volume Retention = 35 days
Maximum Volume Bytes = 10G
Maximum Volumes = 50
Label Format = "Vol-"
}
Schedule {
Name = "NightlyCycle"
Run = Full 1st sun at 01:05
Run = Differential 2nd-5th sun at 01:05
Run = Incremental mon-sat at 01:05
}
FileSet {
Name = "ServerFiles"
Include {
Options {
signature = SHA1
compression = GZIP
}
File = /etc
File = /home
File = /var/www
}
Exclude {
File = /var/lib/bacula
File = /tmp
}
}
JobDefs {
Name = "NightlyJob"
Type = Backup
Level = Incremental
FileSet = "ServerFiles"
Schedule = "NightlyCycle"
Storage = LocalDisk
Pool = DiskPool
Messages = Standard
Priority = 10
Write Bootstrap = "/var/lib/bacula/%c.bsr"
}
Client {
Name = local-fd
Address = 127.0.0.1
FDPort = 9102
Catalog = MyCatalog
Password = "your_local_fd_password"
File Retention = 35 days
Job Retention = 6 months
AutoPrune = yes
}
Job {
Name = "backup-local"
JobDefs = "NightlyJob"
Client = local-fd
}
What these resources do:
- Storage tells the Director how to reach the Storage Daemon and which device to use. The address must be reachable by every client, which is why it is the private IP and not
127.0.0.1. - Pool groups volumes. Each volume file holds up to 10 GB; after 35 days a volume can be recycled, so disk usage stays bounded at about 50 x 10 GB.
- Schedule runs a full backup on the first Sunday of the month, differentials on other Sundays and incrementals the rest of the week.
- FileSet lists what to back up. Add the directory where you keep database dumps, since Bacula should copy dumps rather than live database files.
- JobDefs holds defaults shared by jobs, and each Job only adds its client.
MyCatalog and Standard are defined in the default Director configuration. Also keep the volume retention (35 days) at least as long as a full backup cycle, otherwise a volume holding the last full backup could be recycled before the next one runs.
Test the configuration and restart the Director:
sudo bacula-dir -t -c /etc/bacula/bacula-dir.conf
sudo systemctl restart bacula-director
NoteThe default configuration also contains example jobs such as
BackupClient1, which run on their own schedule to the default storage. Once your own job works, comment out the exampleJobblocks you do not use to keep the job list clean.
Step 5 - Running the first backup
Start the console:
sudo bconsole
At the * prompt, start a full backup of the local server and wait for it to finish:
run job=backup-local level=Full yes
wait
messages
The messages output ends with a job report. The important lines are:
Job: backup-local.2026-09-25_10.42.18_03
Backup Level: Full
FileSet: "ServerFiles" 2026-09-25 10:42:18
Pool: "DiskPool" (From Job resource)
Storage: "LocalDisk" (From Job resource)
Volume name(s): Vol-0001
SD Files Written: 3,214
Termination: Backup OK
Termination: Backup OK is what you are looking for. Bacula created and labeled Vol-0001 automatically. Check the job list and the volumes:
list jobs
list volumes pool=DiskPool
Type exit to leave the console. On disk, the volume is a regular file:
sudo ls -lh /srv/bacula/volumes
-rw-r----- 1 bacula tape 58M Sep 25 10:42 Vol-0001
Step 6 - Restoring files
Test a restore right away. Restores go to a separate directory by default, so nothing live is overwritten. In bconsole, run:
restore client=local-fd fileset=ServerFiles where=/tmp/bacula-restores select current all done yes
wait
messages
This selects the most recent backup of local-fd, marks all files, and runs the default RestoreFiles job. The report should end with Termination: Restore OK.
Compare the restored copy with the original:
sudo diff -r /etc/ssh /tmp/bacula-restores/etc/ssh && echo "Restore matches"
Restore matches
To restore only some files, run restore without arguments. Choose option 5 (Select the most recent backup for a client), pick the client, and Bacula opens a file browser where you use cd, ls, mark <file> and finally done. Before confirming, type mod to change the restore location (Where) if needed.
Clean up the test restore:
sudo rm -rf /tmp/bacula-restores
Step 7 - Adding a remote client
On the client (web1), install only the File Daemon:
sudo apt update
sudo apt install bacula-fd
Generate a password for this client:
openssl rand -base64 33
Edit the File Daemon configuration:
sudo nano /etc/bacula/bacula-fd.conf
In the first Director block, set Name to your Director's name from Step 2 and Password to the new password:
Director {
Name = backup-dir
Password = "your_web1_fd_password"
}
In the FileDaemon block, change FDAddress from 127.0.0.1 to the client's private IP, so the Director can reach it over the private network:
FDAddress = 10.0.0.21
Leave the other directives in the file as they are. Test, restart and allow the Director to connect:
sudo bacula-fd -t -c /etc/bacula/bacula-fd.conf
sudo systemctl restart bacula-fd
sudo ufw allow from 10.0.0.10 to any port 9102 proto tcp
On the backup server, allow the client to reach the Storage Daemon:
sudo ufw allow from 10.0.0.21 to any port 9103 proto tcp
Then add the client and its job to /etc/bacula/conf.d/local.conf:
Client {
Name = web1-fd
Address = 10.0.0.21
FDPort = 9102
Catalog = MyCatalog
Password = "your_web1_fd_password"
File Retention = 35 days
Job Retention = 6 months
AutoPrune = yes
}
Job {
Name = "backup-web1"
JobDefs = "NightlyJob"
Client = web1-fd
}
Test and reload the Director:
sudo bacula-dir -t -c /etc/bacula/bacula-dir.conf
sudo systemctl restart bacula-director
In bconsole, check that the Director can talk to the client, then run a first backup:
status client=web1-fd
run job=backup-web1 level=Full yes
wait
messages
status client prints the client's version and No Jobs running. The backup report should again end with Termination: Backup OK.
Step 8 - Checking scheduled jobs
Both jobs now run every night at 01:05. See what is planned for the next days:
status director
The Scheduled Jobs section lists backup-local and backup-web1 with their next level and time. The default configuration also includes a BackupCatalog job that dumps the catalog database after the nightly jobs; keep it, because without the catalog restores are far more work.
Review past jobs any time with list jobs or, for failures only, list jobs jobstatus=f.
Troubleshooting
Authorization key rejected or Director authorization problem. A name or password does not match. The Director name in each daemon's Director block must equal the Name in bacula-dir.conf, and each password in conf.d/local.conf must equal the one in that daemon's Director block.
Could not connect to Storage daemon on 10.0.0.10:9103. The client cannot reach the Storage Daemon. Check SDAddress in bacula-sd.conf, that bacula-sd listens on the private IP (sudo ss -tlnp | grep 9103) and the UFW rule for the client.
The job waits with Cannot find any appendable volumes. The pool is full: all volumes are in use and none has passed its retention. Increase Maximum Volumes, or lower the retention. After changing a Pool resource, run update pool=DiskPool in bconsole so existing catalog records pick up the new values.
Director messages are also written to /var/log/bacula/bacula.log, and each daemon logs to the journal (sudo journalctl -u bacula-director).
Conclusion
You now have a Bacula backup server on Ubuntu 24.04 with a PostgreSQL catalog, disk-based volumes that are labeled and recycled automatically, nightly full, differential and incremental backups of the server and a remote client, and a tested restore. As next steps, copy the volumes off site with a Copy job to a second Storage Daemon or object storage, enable TLS between the daemons, and add a client for each remaining server with its own Client and Job resources.
