Last active
          October 7, 2025 10:39 
        
      - 
      
- 
        Save danielehrhardt/c1858637b6adfdd83d8fe988ac747d4f to your computer and use it in GitHub Desktop. 
    Ubuntu 24.04: Zabbix 7.4 + MySQL end-to-end installer
  
        
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
  | #!/usr/bin/env bash | |
| # Ubuntu 24.04: Zabbix 7.4 + MySQL end-to-end installer with detailed logging | |
| set -Eeuo pipefail | |
| # ---------- Config ---------- | |
| REPO_DEB_URL="https://repo.zabbix.com/zabbix/7.4/release/ubuntu/pool/main/z/zabbix-release/zabbix-release_latest_7.4+ubuntu24.04_all.deb" | |
| REPO_DEB_FILE="/tmp/zabbix-release_latest_7.4+ubuntu24.04_all.deb" | |
| TZ="Europe/Berlin" # PHP timezone for Zabbix UI | |
| SECRET_FILE="/root/zabbix-db-secret.txt" # Stores generated DB password and access URL | |
| LOG_DIR="/var/log" | |
| STAMP="$(date +%Y%m%d_%H%M%S)" | |
| LOG_FILE="${LOG_DIR}/zabbix_install_${STAMP}.log" | |
| # Make locale predictable so tools like tr/grep behave | |
| export LC_ALL=C | |
| export LANG=C | |
| # ---------- Logging helpers ---------- | |
| mkdir -p "$LOG_DIR" || true | |
| # Redirect all stdout/stderr to both console and log | |
| exec > >(tee -a "$LOG_FILE") 2>&1 | |
| PS4='+ [${BASH_SOURCE##*/}:${LINENO}] ' | |
| log() { printf '\n[%s] %s\n' "$(date -Is)" "$*"; } | |
| step() { printf '\n=== [%s] %s ===\n' "$(date -Is)" "$*"; } | |
| die() { echo "ERROR: $*" >&2; exit 1; } | |
| # Run a command, log it, capture output, and fail loudly if non-zero | |
| run() { | |
| echo "\$ $*" | |
| # shellcheck disable=SC2068 | |
| eval $* 2>&1 | tee -a "$LOG_FILE" | |
| local rc=${PIPESTATUS[0]} | |
| if (( rc != 0 )); then | |
| die "Command failed (rc=${rc}): $*" | |
| fi | |
| } | |
| # Trap for uncaught errors | |
| trap 'echo; echo "!!! An error occurred. See log: ${LOG_FILE}"; echo' ERR | |
| # ---------- Functions ---------- | |
| require_root() { | |
| step "Checking for root privileges" | |
| if [[ $EUID -ne 0 ]]; then | |
| die "Please run as root (e.g., sudo bash $0)" | |
| fi | |
| } | |
| gen_password() { | |
| # Generate a MySQL/INI-friendly 24-char secret; robust against locale issues | |
| step "Generating random database password" | |
| local p="" | |
| if command -v openssl >/dev/null 2>&1; then | |
| p="$(LC_ALL=C openssl rand -base64 48 | tr -dc 'A-Za-z0-9@#%^*_+=-' | head -c 24 || true)" | |
| fi | |
| if [ -z "$p" ] || [ "${#p}" -lt 16 ]; then | |
| p="$(LC_ALL=C tr -dc 'A-Za-z0-9@#%^*_+=-' </dev/urandom | head -c 24 || true)" | |
| fi | |
| if [ -z "$p" ] || [ "${#p}" -lt 16 ]; then | |
| p="$(date +%s%N | sha256sum | cut -c1-24)" | |
| fi | |
| echo "$p" | |
| } | |
| install_base_packages() { | |
| step "Updating APT and installing base packages" | |
| export DEBIAN_FRONTEND=noninteractive | |
| run apt-get update -y | |
| run apt-get install -y wget lsb-release gnupg ca-certificates apt-transport-https curl | |
| } | |
| install_zabbix_repo() { | |
| step "Installing Zabbix repository package" | |
| run wget -O "$REPO_DEB_FILE" "$REPO_DEB_URL" | |
| run dpkg -i "$REPO_DEB_FILE" | |
| run apt-get update -y | |
| } | |
| install_mysql_and_zabbix() { | |
| step "Installing MySQL server and Zabbix components" | |
| run apt-get install -y mysql-server | |
| run systemctl enable --now mysql | |
| run systemctl is-active --quiet mysql | |
| run apt-get install -y \ | |
| zabbix-server-mysql zabbix-frontend-php zabbix-apache-conf zabbix-sql-scripts zabbix-agent | |
| } | |
| mysql_hardening_minimal() { | |
| step "Ensuring MySQL listens locally and is reachable" | |
| # On Ubuntu 24.04 MySQL uses unix socket auth for root; this is fine. | |
| # Just make sure service is up and basic connectivity works. | |
| run mysql --protocol=socket -e "SELECT VERSION();" || true | |
| # Ensure bind-address keeps MySQL local (security best practice for single-host Zabbix) | |
| local cnf="/etc/mysql/mysql.conf.d/mysqld.cnf" | |
| if grep -q '^[#[:space:]]*bind-address' "$cnf"; then | |
| run sed -i 's/^[#[:space:]]*bind-address.*/bind-address = 127.0.0.1/' "$cnf" | |
| else | |
| echo "bind-address = 127.0.0.1" >> "$cnf" | |
| fi | |
| run systemctl restart mysql | |
| run systemctl is-active --quiet mysql | |
| } | |
| create_db_user_and_import() { | |
| local ZABBIX_DB_PASS="$1" | |
| step "Creating Zabbix database and user; importing schema" | |
| # Create DB and user using root socket auth | |
| run mysql -e "CREATE DATABASE IF NOT EXISTS zabbix CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;" | |
| run mysql -e "CREATE USER IF NOT EXISTS 'zabbix'@'localhost' IDENTIFIED BY '${ZABBIX_DB_PASS}';" | |
| run mysql -e "GRANT ALL PRIVILEGES ON zabbix.* TO 'zabbix'@'localhost';" | |
| run mysql -e "FLUSH PRIVILEGES;" | |
| run mysql -e "SET GLOBAL log_bin_trust_function_creators = 1;" | |
| # Import schema + data | |
| local schema="/usr/share/zabbix/sql-scripts/mysql/server.sql.gz" | |
| [[ -f "$schema" ]] || die "Schema file not found at ${schema}" | |
| run zcat "$schema" \| mysql --default-character-set=utf8mb4 -uzabbix -p"$(printf %q "$ZABBIX_DB_PASS")" zabbix | |
| # Disable again | |
| run mysql -e "SET GLOBAL log_bin_trust_function_creators = 0;" | |
| } | |
| configure_zabbix_server() { | |
| local ZABBIX_DB_PASS="$1" | |
| local CFG="/etc/zabbix/zabbix_server.conf" | |
| step "Configuring /etc/zabbix/zabbix_server.conf" | |
| # Ensure DB settings | |
| if grep -qE '^[#[:space:]]*DBName=' "$CFG"; then | |
| run sed -i 's/^[#[:space:]]*DBName=.*/DBName=zabbix/' "$CFG" | |
| else | |
| echo "DBName=zabbix" >> "$CFG" | |
| fi | |
| if grep -qE '^[#[:space:]]*DBUser=' "$CFG"; then | |
| run sed -i 's/^[#[:space:]]*DBUser=.*/DBUser=zabbix/' "$CFG" | |
| else | |
| echo "DBUser=zabbix" >> "$CFG" | |
| fi | |
| if grep -qE '^[#[:space:]]*DBPassword=' "$CFG"; then | |
| run sed -i "s/^[#[:space:]]*DBPassword=.*/DBPassword=${ZABBIX_DB_PASS}/" "$CFG" | |
| else | |
| echo "DBPassword=${ZABBIX_DB_PASS}" >> "$CFG" | |
| fi | |
| } | |
| configure_php_timezone_for_zabbix() { | |
| local CONF="/etc/zabbix/apache.conf" | |
| step "Ensuring PHP timezone is set to ${TZ} for Zabbix UI" | |
| if [[ -f "$CONF" ]]; then | |
| if grep -qE '^\s*php_value\s+date\.timezone' "$CONF"; then | |
| run sed -i "s#^\s*php_value\s\+date\.timezone.*# php_value date.timezone ${TZ}#" "$CONF" | |
| else | |
| cat >>"$CONF" <<EOF | |
| # Set PHP timezone for Zabbix UI | |
| <Directory "/usr/share/zabbix"> | |
| php_value date.timezone ${TZ} | |
| </Directory> | |
| EOF | |
| fi | |
| else | |
| echo "WARN: ${CONF} not found; skipping timezone injection" | |
| fi | |
| } | |
| restart_and_enable_services() { | |
| step "Restarting and enabling Zabbix server/agent and Apache" | |
| run systemctl restart zabbix-server zabbix-agent apache2 | |
| run systemctl enable zabbix-server zabbix-agent apache2 | |
| run systemctl is-active --quiet zabbix-server | |
| run systemctl is-active --quiet zabbix-agent | |
| run systemctl is-active --quiet apache2 | |
| } | |
| maybe_configure_ufw() { | |
| step "Configuring UFW (if active)" | |
| if command -v ufw >/dev/null 2>&1 && ufw status | grep -q "Status: active"; then | |
| run ufw allow 80/tcp || true | |
| run ufw allow 10050/tcp || true | |
| run ufw allow 10051/tcp || true | |
| else | |
| echo "UFW not active; skipping firewall changes." | |
| fi | |
| } | |
| save_secrets() { | |
| local ZABBIX_DB_PASS="$1" | |
| step "Saving secrets to ${SECRET_FILE}" | |
| umask 077 | |
| cat > "$SECRET_FILE" <<EOF | |
| Zabbix MySQL credentials (generated $(date -Is)) | |
| ----------------------------------------------- | |
| DB Host : localhost | |
| DB Name : zabbix | |
| DB User : zabbix | |
| DB Pass : ${ZABBIX_DB_PASS} | |
| Zabbix Web (Apache): | |
| http://$(hostname -I | awk '{print $1}')/zabbix | |
| Log file for this installation: | |
| ${LOG_FILE} | |
| EOF | |
| chmod 600 "$SECRET_FILE" | |
| echo "Secrets written." | |
| } | |
| final_checks() { | |
| step "Final checks" | |
| echo "- MySQL status:"; systemctl --no-pager --full status mysql | sed -n '1,12p' || true | |
| echo "- Zabbix server status:"; systemctl --no-pager --full status zabbix-server | sed -n '1,12p' || true | |
| echo "- Zabbix agent status:"; systemctl --no-pager --full status zabbix-agent | sed -n '1,12p' || true | |
| echo "- Apache status:"; systemctl --no-pager --full status apache2 | sed -n '1,12p' || true | |
| } | |
| # ---------- Main ---------- | |
| main() { | |
| step "Starting Zabbix 7.4 + MySQL installation (log: ${LOG_FILE})" | |
| require_root | |
| install_base_packages | |
| install_zabbix_repo | |
| install_mysql_and_zabbix | |
| mysql_hardening_minimal | |
| ZABBIX_DB_PASS="$(gen_password)" | |
| echo "Generated password length: ${#ZABBIX_DB_PASS}" # visible sanity check | |
| create_db_user_and_import "$ZABBIX_DB_PASS" | |
| configure_zabbix_server "$ZABBIX_DB_PASS" | |
| configure_php_timezone_for_zabbix" | |
| restart_and_enable_services | |
| maybe_configure_ufw | |
| save_secrets "$ZABBIX_DB_PASS" | |
| final_checks | |
| step "Done ✅" | |
| echo "Open the Zabbix UI at: http://$(hostname -I | awk '{print $1}')/zabbix" | |
| echo "Credentials saved at: ${SECRET_FILE}" | |
| echo "Installer log: ${LOG_FILE}" | |
| } | |
| main "$@" | 
  
    Sign up for free
    to join this conversation on GitHub.
    Already have an account?
    Sign in to comment