You have a handful of Ubuntu servers running in production, and right now the only way you know something is wrong is when a user complains or a service crashes. Disk space quietly fills up, memory creeps toward its limit, a process dies at 3 AM, and nobody notices until the damage is already done. What you need is a system that watches your servers continuously and tells you when something crosses a threshold, before it becomes an outage.
This tutorial walks you through setting up Zabbix, a mature, self-hosted monitoring platform that has been used in production environments for over two decades. You will install a Zabbix server with a MySQL database backend and an Nginx-based web frontend, install the lightweight zabbix-agent2 on a separate host you want to monitor, register that host in the Zabbix UI, and configure a trigger that alerts you when disk usage gets too high.
This guide is for sysadmins, DevOps engineers, and developers who manage a small fleet of Linux servers and want centralized visibility without relying on a third-party SaaS product. You should be comfortable running commands with sudo, editing configuration files with a terminal editor, and have basic familiarity with MySQL. No prior Zabbix experience is required.
Conceptual Overview
Zabbix is built around a handful of core pieces that fit together like this:
Zabbix server is the central process that polls hosts for data, evaluates trigger conditions, stores everything in a database, and fires alerts (called actions) when something goes wrong. It is the brain of the whole setup and runs on one dedicated machine.
Zabbix agent is a small daemon you install on every host you want to monitor. The modern version, zabbix-agent2, written in Go, collects local metrics such as CPU load, memory usage, disk space, and network traffic, and reports them back to the server either when polled (passive checks) or by pushing data itself (active checks). Active checks, which this tutorial uses, scale better because the agent initiates the connection instead of the server having to reach out to every host individually.
Zabbix frontend is the PHP web application you interact with day to day: it displays dashboards, lets you configure hosts and triggers, and shows the history of collected data. It talks to the same database the server writes to, but it is a separate component that needs its own web server (Nginx or Apache) and PHP-FPM.
Items, triggers, and actions are the three building blocks of monitoring logic in Zabbix. An item defines a single piece of data to collect, for example “free disk space on /”. A trigger is a boolean expression evaluated against item data, for example “free space on / is below 10%”. An action decides what happens when a trigger fires, most commonly sending an email or a message to a chat platform. Zabbix ships with a large set of predefined items and triggers bundled into templates, which you attach to a host instead of building every check from scratch.
Unlike a metrics stack such as Prometheus and Grafana (covered in this guide), where you typically run exporters and a separate visualization layer, Zabbix bundles collection, storage, alerting, and visualization into a single product with one web UI. That makes it a good fit if you want a monitoring solution that is ready to alert on real infrastructure problems (disk, memory, service status) within the first hour, without wiring together multiple tools.
Prerequisites
Before starting, make sure you have:
- Two Ubuntu 22.04 LTS servers: one for the Zabbix server (this tutorial uses
10.20.0.20, hostnamezbx-server), and one host to monitor (this tutorial uses10.20.0.21, hostnameapp-01). You can monitor more hosts later using the same agent steps. - A regular user with
sudoprivileges on both machines. - At least 2 GB of RAM and 20 GB of free disk on the Zabbix server node. The monitored host has no special requirements.
- Outbound internet access on both servers to install packages.
- Network connectivity between the two servers on TCP port 10051 (agent to server, active checks) and TCP port 10050 (server to agent, passive checks and initial discovery).
Step-by-Step Hands-On Tutorial
Step 1: Install the Zabbix repository on the server
Zabbix publishes its own APT repository so you get a current release instead of whatever Ubuntu ships. On zbx-server:
wget https://repo.zabbix.com/zabbix/6.4/ubuntu/pool/main/z/zabbix-release/zabbix-release_6.4-1+ubuntu22.04_all.deb
sudo dpkg -i zabbix-release_6.4-1+ubuntu22.04_all.deb
sudo apt update
The .deb package does nothing but add the Zabbix repository and signing key to your system, it does not install Zabbix itself.
Step 2: Install the Zabbix server, frontend, and agent packages
sudo apt install -y zabbix-server-mysql zabbix-frontend-php zabbix-nginx-conf zabbix-sql-scripts zabbix-agent2 mysql-server
This installs the server process, the PHP frontend, an Nginx config snippet tailored for Zabbix, the SQL files needed to create the database schema, the agent (since the server itself can also monitor its own health), and MySQL as the database backend.
Step 3: Create the Zabbix database
Log in to MySQL and create a dedicated database and user:
sudo mysql -uroot
create database zabbix character set utf8mb4 collate utf8mb4_bin;
create user 'zabbix'@'localhost' identified by 'ChangeThisPassword123!';
grant all privileges on zabbix.* to 'zabbix'@'localhost';
set global log_bin_trust_function_creators = 1;
quit;
Using utf8mb4 avoids character encoding issues later, and log_bin_trust_function_creators is required for the schema import to succeed if binary logging is enabled.
Now import the base schema, which is fairly large and takes a minute or two:
zcat /usr/share/zabbix-sql-scripts/mysql/server.sql.gz | sudo mysql --default-character-set=utf8mb4 -uzabbix -p zabbix
Enter the password you set above when prompted. Once it finishes, you can disable the trust setting again for safety:
sudo mysql -uroot -e "set global log_bin_trust_function_creators = 0;"
Step 4: Point Zabbix server at the database
Edit /etc/zabbix/zabbix_server.conf and set the database password:
sudo nano /etc/zabbix/zabbix_server.conf
Find and update this line:
DBPassword=ChangeThisPassword123!
Everything else in this file (DBHost, DBName, DBUser) already defaults to localhost, zabbix, and zabbix, which matches what you created in Step 3.
Step 5: Configure PHP for the Zabbix frontend
The frontend needs a specific timezone set in PHP so scheduling and history graphs display correctly. Edit the Zabbix Nginx/PHP config:
sudo nano /etc/zabbix/nginx.conf
Uncomment and set the listen and server_name directives:
listen 80;
server_name zbx-server;
Then open the PHP-FPM pool config used by Zabbix and set your timezone:
sudo nano /etc/zabbix/php-fpm.conf
Uncomment this line and set it to your region, for example:
php_value[date.timezone] = Asia/Jakarta
Step 6: Start all the services
sudo systemctl restart zabbix-server zabbix-agent2 nginx php8.1-fpm
sudo systemctl enable zabbix-server zabbix-agent2 nginx php8.1-fpm
Check that the server started correctly:
sudo systemctl status zabbix-server --no-pager
You should see Active: active (running). If it immediately exits, check /var/log/zabbix/zabbix_server.log for a database connection error, almost always caused by a wrong password in Step 4.
Step 7: Finish setup through the web UI
Open http://10.20.0.20 in your browser. The setup wizard walks you through:
- A welcome screen, click Next step.
- A prerequisites check, everything should show OK in green. If PHP timezone shows a warning, go back and re-check Step 5.
- Database connection: host
localhost, database namezabbix, userzabbix, and the password from Step 3. - Zabbix server details: host
localhost, port10051(defaults are fine since the frontend and server run on the same box). - A summary screen, click Next step, then Finish.
Log in with the default credentials, username Admin, password zabbix, and change that password immediately under Users → Administration → Users → Admin → Change password.
Step 8: Install the agent on the host you want to monitor
On app-01 (10.20.0.21), install the same repository and the agent package:
wget https://repo.zabbix.com/zabbix/6.4/ubuntu/pool/main/z/zabbix-release/zabbix-release_6.4-1+ubuntu22.04_all.deb
sudo dpkg -i zabbix-release_6.4-1+ubuntu22.04_all.deb
sudo apt update
sudo apt install -y zabbix-agent2
Edit /etc/zabbix/zabbix_agent2.conf and set these three values:
sudo nano /etc/zabbix/zabbix_agent2.conf
Server=10.20.0.20
ServerActive=10.20.0.20
Hostname=app-01
Server controls which IP is allowed to poll this agent (passive checks), ServerActive is where the agent sends its own data (active checks), and Hostname must exactly match the host name you will register in the Zabbix UI in the next step.
Restart the agent:
sudo systemctl restart zabbix-agent2
sudo systemctl enable zabbix-agent2
If you have ufw enabled, allow the Zabbix server to reach the agent:
sudo ufw allow from 10.20.0.20 to any port 10050 proto tcp
Step 9: Register the host in Zabbix
Back in the web UI, go to Data collection → Hosts → Create host. Fill in:
- Host name:
app-01(must matchHostnamein the agent config exactly) - Host groups: create a new group called
App Servers - Interfaces: add an Agent interface with IP
10.20.0.21, port10050
Under Templates, click Select, search for Linux by Zabbix agent, and add it. This one template gives you CPU, memory, disk, and network metrics without configuring anything manually.
Click Add to save the host. Within a minute or two, go to Monitoring → Latest data, filter by host app-01, and you should see live values coming in for metrics like CPU utilization and Free disk space on /.
Step 10: Add a disk space alert
The bundled template already includes a trigger that fires when a filesystem drops below 20% free space, but let’s walk through creating one manually so you understand how it works. Go to Data collection → Hosts, click Triggers next to app-01, then Create trigger.
- Name:
Root filesystem low on space on {HOST.NAME} - Severity:
High - Expression: click Add, choose item
Free disk space on / (percentage), functionlast(), operator<, value10
The resulting expression looks like:
last(/app-01/vfs.fs.size[/,pfree])<10
Save it. Now go to Alerts → Actions → Trigger actions and edit the default Report problems to Zabbix administrators action, or create a new one, to send an email or a message to a channel your team monitors when this trigger fires. You will need to configure an SMTP media type first under Alerts → Media types → Email.
Common Mistakes & Troubleshooting
Frontend prerequisites page shows a red PHP timezone error. You edited the wrong PHP-FPM pool file, or forgot to restart php8.1-fpm after editing it. Confirm the PHP version installed with php -v and make sure you edited the matching /etc/zabbix/php-fpm.conf (this file is separate from the system-wide php.ini).
Zabbix server fails to start with a database error. Almost always a mismatched password between /etc/zabbix/zabbix_server.conf and what you set in MySQL. Check /var/log/zabbix/zabbix_server.log for the exact error and re-run sudo mysql -uzabbix -p zabbix manually to confirm the credentials work.
Host shows as unreachable, no data in Latest data. The most common cause is Hostname in zabbix_agent2.conf not matching the host name typed into the Zabbix UI. These are case-sensitive and must match exactly. Also check that the firewall on app-01 allows inbound connections from the Zabbix server on port 10050, and outbound connections to port 10051 for active checks.
Import of server.sql.gz seems to hang. It is not hanging, the base schema is large and importing it commonly takes one to three minutes on modest hardware. Let it finish rather than interrupting it, since a partial import leaves the database in a broken state you will need to drop and recreate.
Triggers never fire even though disk is full. Zabbix evaluates triggers only after it has received item data. Check Monitoring → Latest data first to confirm the item itself is updating; if the item has no recent data, the trigger has nothing to evaluate.
Best Practices
Change the default Admin password immediately after the first login, and create individual named accounts for each team member instead of sharing one login, so actions are auditable.
Use host groups deliberately (App Servers, Database Servers, Load Balancers) since they let you scope permissions, apply templates in bulk, and filter dashboards without hunting through a flat list of hosts.
Prefer active checks (ServerActive) over passive checks for most metrics. Active checks scale to hundreds of hosts more gracefully because the Zabbix server is not the one initiating every poll, and they tolerate agents behind NAT more easily.
Set the database backup on the same cadence you would for any production database, since it holds both your configuration and your historical monitoring data. A mysqldump of the zabbix database on a nightly cron job, shipped off the server, is a reasonable starting point.
Tune history and trend storage periods (Data collection → Hosts → Items, or globally under housekeeping settings) so old raw data gets purged automatically. Left at defaults, the database can grow much larger than necessary for a small deployment.
Do not expose the Zabbix frontend directly to the internet. Put it behind a VPN, restrict access by IP in your firewall or Nginx config, or place it behind an authenticating reverse proxy.
Conclusion
You now have a working Zabbix deployment: a server with a MySQL backend and an Nginx frontend, an agent reporting live metrics from a remote host, and a trigger that will alert you before a full disk turns into an outage. This same pattern, install the agent, register the host, attach a template, adjust or add triggers, scales to dozens or hundreds of servers without changing the underlying workflow.
From here, a good next step is exploring Zabbix’s network discovery rules to auto-register new hosts as you provision them, wiring up a chat-based media type (Slack or Telegram) so alerts land where your team already looks, and building a few custom dashboards under Monitoring → Dashboards so the state of your infrastructure is visible at a glance rather than buried in individual host pages.