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 sudo privileges on both.
  • A private network between them. The examples use 10.0.0.10 for the backup server and 10.0.0.21 for the client web1.

Understanding the Bacula components

ComponentPackagePortRuns on
Director (bacula-dir)bacula-director-pgsql9101Backup server
Storage Daemon (bacula-sd)bacula-sd9103Backup server
File Daemon (bacula-fd)bacula-fd9102Every machine you back up
Console (bconsole)bacula-console-Wherever you administer from
CatalogPostgreSQL5432 (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

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.