Zammad is an open-source helpdesk and ticketing system: it turns emails, web forms, chat and phone calls into tickets that your team assigns, answers and tracks in one web interface. In this tutorial you will deploy Zammad on Ubuntu 24.04 using the official zammad-docker-compose stack, which already bundles PostgreSQL, Elasticsearch, Redis, Memcached and the Zammad services. You will then publish it through Nginx with a Let's Encrypt certificate, finish the setup wizard and connect a support mailbox.

Prerequisites

To follow this guide you need:

  • A server running Ubuntu 24.04 LTS, for example a CubePath VPS, with at least 4 GB of RAM (8 GB is recommended once Elasticsearch has real data) and 2 CPU cores.
  • A non-root user with sudo privileges.
  • A domain or subdomain such as helpdesk.your_domain with a DNS A record pointing to your_server_ip.
  • Ports 22, 80 and 443 reachable from the Internet.
  • An email account (IMAP and SMTP credentials) that customers will write to, for example support@your_domain.

Throughout the guide, replace helpdesk.your_domain with your own hostname.

Step 1 - Installing Docker Engine and Docker Compose

Ubuntu's docker.io and docker-compose packages lag behind upstream, and the old docker-compose v1 binary is no longer maintained. Install Docker Engine and the Compose plugin from Docker's official repository instead.

Add Docker's signing key:

sudo apt update
sudo apt install ca-certificates curl git
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

Add the repository:

echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

Install the engine and the Compose plugin:

sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Check that the service is running and that Compose v2 is available:

sudo systemctl is-active docker
docker compose version
active
Docker Compose version v2.39.2

Your version number will differ. All commands in this guide use docker compose (with a space), not the legacy docker-compose.

Step 2 - Preparing the kernel for Elasticsearch

Zammad uses Elasticsearch for its search, and Elasticsearch refuses to start in production mode if the kernel's vm.max_map_count limit is below 262144. Set it permanently with a sysctl drop-in file:

echo "vm.max_map_count=262144" | sudo tee /etc/sysctl.d/99-zammad.conf
sudo sysctl --system

Verify the new value:

sysctl vm.max_map_count
vm.max_map_count = 262144

Step 3 - Downloading the Zammad Compose stack

The Zammad team maintains a ready-made Compose project. Clone it into /opt:

sudo git clone https://github.com/zammad/zammad-docker-compose.git /opt/zammad-docker-compose
cd /opt/zammad-docker-compose

The repository contains docker-compose.yml and a .env.dist file that documents every supported variable with its default value. Do not edit those two files: they are overwritten when you update. Put your own values in a .env file instead, which Docker Compose reads automatically.

Review the available variables first:

cat .env.dist

Create your .env file:

sudo nano /opt/zammad-docker-compose/.env

Add the following, replacing your_strong_password with a long random password:

NGINX_EXPOSE_PORT=127.0.0.1:8080
POSTGRES_PASS=your_strong_password

NGINX_EXPOSE_PORT binds the stack's web entry point to localhost only. This matters because Docker publishes ports by writing its own iptables rules, which bypass UFW: a port published on 0.0.0.0 would be reachable from the Internet even with UFW enabled. The host's Nginx, which you configure in Step 5, will be the only public entry point. Setting POSTGRES_PASS before the first start replaces the default database password.

Step 4 - Starting Zammad

Pull the images and start the stack in the background:

sudo docker compose up -d

The first start takes a few minutes: the zammad-init container creates the database schema and the search index, then exits. Check the status of the containers:

sudo docker compose ps

All services except zammad-init should show running (or Up). zammad-init finishing with exit code 0 is expected. If something keeps restarting, read its logs:

sudo docker compose logs -f zammad-init

Confirm that the web entry point only listens on localhost:

sudo ss -tlnp | grep 8080
LISTEN 0      4096       127.0.0.1:8080       0.0.0.0:*    users:(("docker-proxy",pid=4121,fd=4))

If you see 0.0.0.0:8080 instead, your .env value was not applied; fix it and run sudo docker compose up -d again.

Finally, check that Zammad answers:

curl -sI http://127.0.0.1:8080 | head -n 1
HTTP/1.1 200 OK

Step 5 - Configuring Nginx as a reverse proxy

Install Nginx and Certbot on the host:

sudo apt install nginx certbot python3-certbot-nginx

Create a server block for the helpdesk:

sudo nano /etc/nginx/sites-available/zammad

Paste the following configuration. Zammad uses WebSockets for live updates in the agent interface, so the Upgrade and Connection headers must be forwarded:

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {
    listen 80;
    listen [::]:80;
    server_name helpdesk.your_domain;

    client_max_body_size 50M;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 300s;
    }
}

client_max_body_size allows attachments up to 50 MB. If another site on this server already defines a map named $connection_upgrade, remove the map block from this file to avoid a duplicate definition.

Enable the site, test the syntax and reload Nginx:

sudo ln -s /etc/nginx/sites-available/zammad /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

If UFW is active, allow SSH and web traffic:

sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw status

Step 6 - Enabling HTTPS with Let's Encrypt

Request a certificate. Certbot validates the domain over port 80, then adds the TLS configuration and an HTTP to HTTPS redirect to the server block:

sudo certbot --nginx -d helpdesk.your_domain

Certbot installs a systemd timer that renews certificates automatically. Confirm that renewal works:

sudo certbot renew --dry-run
Congratulations, all simulated renewals succeeded:
  /etc/letsencrypt/live/helpdesk.your_domain/fullchain.pem (success)

Step 7 - Running the setup wizard

Open https://helpdesk.your_domain in your browser. Zammad starts its setup wizard:

  1. Choose Set up a new system.
  2. Create the administrator account with your name, email and a strong password.
  3. Enter your organization name, upload a logo if you want, and confirm the System URL. It must be https://helpdesk.your_domain, because Zammad uses it to build links in notification emails.
  4. Configure email notifications. Select SMTP and enter the outgoing server of the mailbox Zammad will send from. You can skip this step and do it later.
  5. Connect channels. You can add the email channel here or follow Step 8.

After the wizard you land in the agent dashboard. Go to Admin > System > Base and check that Fully qualified domain name is helpdesk.your_domain and HTTP type is https. If these are wrong, links in emails will point to the wrong address.

Step 8 - Connecting a support mailbox

The email channel is what turns messages sent to support@your_domain into tickets.

  1. Go to Admin > Channels > Email and click Add Account.
  2. Enter the display name, the email address and its password. Zammad tries to detect the IMAP and SMTP servers automatically.
  3. If detection fails, fill in the inbound (IMAP, usually port 993 with SSL) and outbound (SMTP, usually port 465 or 587) settings from your mail provider.
  4. Choose the Destination Group that new tickets from this address will land in, for example Users.

Zammad fetches new mail about once a minute. To verify the channel, send an email to support@your_domain from another account and wait for the ticket to appear in Overviews. Reply to it from Zammad and confirm that the reply arrives in the sender's inbox.

With the channel working, create agent accounts under Admin > Manage > Users, assign them to groups, and define service levels under Admin > Manage > SLAs if you need response-time targets.

Step 9 - Backups and updates

The Compose stack includes a zammad-backup service that periodically dumps the PostgreSQL database and the Zammad files into a Docker volume. Find the volume:

sudo docker volume ls | grep backup
local     zammad-docker-compose_zammad-backup

List its contents to confirm that backups are being written:

sudo docker run --rm -v zammad-docker-compose_zammad-backup:/backup alpine ls -lh /backup

These backups still live on the same server. Copy them off the machine regularly, for example with rsync to another host or to object storage.

To update Zammad, pull the latest version of the Compose project and recreate the containers:

cd /opt/zammad-docker-compose
sudo git pull
sudo docker compose pull
sudo docker compose up -d

Read the release notes before a major version upgrade, and make sure a recent backup exists first.

Troubleshooting

The Elasticsearch container keeps restarting. Check its logs with sudo docker compose logs zammad-elasticsearch. The usual causes are max virtual memory areas vm.max_map_count [65530] is too low (repeat Step 2) or the server running out of memory. Elasticsearch needs at least 1 to 2 GB of RAM for itself.

The browser shows 502 Bad Gateway. Nginx cannot reach the stack. Run sudo docker compose ps in /opt/zammad-docker-compose and curl -sI http://127.0.0.1:8080 on the server. During the first start Zammad needs a few minutes before it answers.

The agent interface keeps showing a connection warning. WebSocket traffic is not being proxied. Make sure the Upgrade and Connection headers are in your Nginx configuration, then run sudo nginx -t && sudo systemctl reload nginx.

Emails are not converted into tickets. Open Admin > Channels > Email: Zammad shows the last error for each account. Wrong credentials or an app password requirement on the mail provider are the most common causes.

Conclusion

You now have Zammad running on Ubuntu 24.04 from the official Compose stack, published only through Nginx with HTTPS, and receiving tickets from a support mailbox. As next steps, set up triggers and text modules under Admin > Manage to automate replies, add the web form or chat channel to your website, and schedule an off-server copy of the backup volume.