Zoneminder Install

This role installs and configures ZoneMinder, an open-source video surveillance and NVR (Network Video Recorder) system, along with its dependencies including Apache web server and MariaDB database.

Ansible Apache Bash Debian HTTPS JSON MariaDB MySQL

Zoneminder Install Role

Overview

This role installs and configures ZoneMinder, an open-source video surveillance and NVR (Network Video Recorder) system, along with its dependencies including Apache web server and MariaDB database. It handles complete setup including database creation and schema import, Apache virtual host configuration with SSL support, CGI and rewrite module enablement, filesystem permissions, and service management. The role provides a production-ready ZoneMinder installation accessible via HTTP and HTTPS.

Purpose

  • NVR System: Deploy video surveillance and recording platform
  • Complete Stack: Install and configure Apache, MariaDB, ZoneMinder
  • Database Setup: Create database, user, and import schema
  • Web Interface: Configure Apache with HTTP/HTTPS access
  • API Support: Enable ZoneMinder API for automation
  • Secure Configuration: SSL support and proper file permissions
  • Service Management: Enable and start all required services

Requirements

  • Ansible 2.9 or higher
  • Target system: Debian/Ubuntu (tested on Ubuntu)
  • Root or sudo privileges
  • Sufficient disk space for video recordings (recommend 500GB+)
  • SSL certificates (if using HTTPS)
  • Database password stored in Ansible Vault

What is ZoneMinder?

ZoneMinder is an open-source video surveillance system:

Key features:

  • Multi-camera support (IP cameras, USB cameras)
  • Motion detection and alerting
  • Continuous or triggered recording
  • Web-based interface
  • REST API for automation
  • Event management and playback
  • Live viewing and PTZ control
  • Mobile app support

Use cases:

  • Home security monitoring
  • Business surveillance
  • Wildlife monitoring
  • Time-lapse recording

Compared to alternatives:

  • vs Frigate: More mature, broader camera support
  • vs Blue Iris: Open-source, Linux-based
  • vs Shinobi: More established, better documentation

Role Variables

Optional Variables

VariableDefaultDescription
zoneminder_install_db_namezmMariaDB database name
zoneminder_install_db_userzmuserDatabase username
zoneminder_install_db_password{{ vault_zoneminder_db_password }}Database password (vault)
zoneminder_install_db_hostlocalhostDatabase host
zoneminder_install_ssl_cert_file/etc/ssl/certs/zoneminder.pemSSL certificate path
zoneminder_install_ssl_key_file/etc/ssl/private/zoneminder.keySSL private key path
zoneminder_install_config_file/etc/zm/zm.confZoneMinder config file

Variable Details

zoneminder_install_db_password

MariaDB database password for ZoneMinder user.

IMPORTANT: Store in Ansible Vault

# In vault.yml
vault_zoneminder_db_password: "secure_random_password_here"

# In playbook
zoneminder_install_db_password: "{{ vault_zoneminder_db_password }}"

Generate secure password:

openssl rand -base64 32

zoneminder_install_ssl_cert_file / zoneminder_install_ssl_key_file

Paths to SSL certificate and private key for HTTPS.

Default paths:

  • Certificate: /etc/ssl/certs/zoneminder.pem
  • Key: /etc/ssl/private/zoneminder.key

Prerequisites: SSL files must exist before running role

Generate self-signed certificate:

openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout /etc/ssl/private/zoneminder.key \
  -out /etc/ssl/certs/zoneminder.pem \
  -subj "/CN=zoneminder.local"

Production: Use Let’s Encrypt or organizational CA

Dependencies

No Ansible role dependencies, but requires:

  • Sufficient disk space for recordings
  • SSL certificates (for HTTPS)
  • Database password in vault

Optionally used with:

  • zoneminder_monitors: Add cameras after installation
  • SSL certificate roles: Generate/deploy certificates
  • backup roles: Backup ZoneMinder database and recordings

Example Playbook

Basic Installation

---
- name: Install ZoneMinder NVR
  hosts: zoneminder
  become: true

  roles:
    - zoneminder_install

With Custom Database

---
- name: Install ZoneMinder with Custom DB
  hosts: zoneminder
  become: true

  vars:
    zoneminder_install_db_name: zoneminder_prod
    zoneminder_install_db_user: zm_admin
    zoneminder_install_db_password: "{{ vault_zm_db_password }}"

  roles:
    - zoneminder_install

Complete Setup with Cameras

---
- name: Install and Configure ZoneMinder
  hosts: zoneminder
  become: true

  roles:
    - zoneminder_install
    - zoneminder_monitors  # Add cameras after install

What This Role Does

1. Install Python MySQL Packages

Installs Python packages required for Ansible MySQL modules:

  • python3-pymysql: Python MySQL client library

Why needed: Ansible community.mysql modules require Python MySQL client

2. Install ZoneMinder Packages

Installs required packages:

  • apache2: Web server
  • mariadb-server: Database server
  • zoneminder: NVR application

Package manager: apt (Debian/Ubuntu)

3. Configure MariaDB

Start MariaDB:

  • Enables MariaDB service
  • Starts MariaDB daemon

Create database:

CREATE DATABASE zm;

Create user:

CREATE USER 'zmuser'@'localhost' IDENTIFIED BY 'password';
GRANT ALL PRIVILEGES ON zm.* TO 'zmuser'@'localhost';
FLUSH PRIVILEGES;

Import schema:

  • Checks if schema already imported (looks for Config table)
  • Imports /usr/share/zoneminder/db/zm_create.sql if needed
  • Idempotent: Won’t re-import if already done

4. Configure ZoneMinder

Set file permissions:

  • File: /etc/zm/zm.conf
  • Owner: root:www-data
  • Mode: 0640 (read-only for www-data group)

Update database password:

  • Sets ZM_DB_PASS=password in /etc/zm/zm.conf
  • Uses lineinfile for idempotent updates

5. Configure Apache

Deploy ZoneMinder Apache config:

  • Template: zoneminder.conf.j2
  • Location: /etc/apache2/conf-available/zoneminder.conf
  • Enables ZoneMinder web application and API

Enable configuration:

a2enconf zoneminder

Enable Apache modules:

  • cgi: For ZoneMinder CGI scripts
  • rewrite: For URL rewriting
  • ssl: For HTTPS support

Deploy virtual hosts:

  • HTTP: 000-default.conf (port 80)
  • HTTPS: default-ssl.conf (port 443)

Enable sites:

a2ensite 000-default
a2ensite default-ssl

6. Start Services

Apache:

  • Enables on boot
  • Starts service
  • Reloads on config changes

ZoneMinder:

  • Enables on boot
  • Starts service
  • Restarts on config changes

Installed Components

Apache Web Server

Purpose: Serve ZoneMinder web interface

Configuration:

  • HTTP on port 80
  • HTTPS on port 443
  • CGI enabled for ZoneMinder scripts
  • Rewrite rules for clean URLs

Document root: /usr/share/zoneminder/www

MariaDB Database

Purpose: Store ZoneMinder configuration and events

Database structure:

  • Monitors: Camera definitions
  • Events: Recorded motion events
  • Frames: Individual frame metadata
  • Zones: Motion detection zones
  • States: System states (recording modes)

Data location: /var/lib/mysql/zm/

ZoneMinder Application

Purpose: Video surveillance and recording

Components:

  • Web interface: Camera viewing and configuration
  • API: REST API for automation
  • Daemons: Background processes for recording
  • Storage: Event storage (/var/cache/zoneminder/)

Default login:

  • Username: admin
  • Password: admin (CHANGE IMMEDIATELY)

File Locations

ComponentPathPurpose
Config file/etc/zm/zm.confMain configuration
Database/var/lib/mysql/zm/MariaDB database files
Events/var/cache/zoneminder/events/Recorded events
Temp images/var/cache/zoneminder/temp/Temporary frames
Apache config/etc/apache2/conf-enabled/zoneminder.confWeb server config
Virtual hosts/etc/apache2/sites-enabled/HTTP/HTTPS sites
Logs/var/log/zm/ZoneMinder logs

Network Access

Web interface URLs:

  • HTTP: http://zoneminder-ip/zm
  • HTTPS: https://zoneminder-ip/zm

API endpoint:

  • https://zoneminder-ip/zm/api/

Default ports:

  • HTTP: 80
  • HTTPS: 443

Firewall rules needed:

# Allow HTTP
- action: pass
  destination_port: "80"
  protocol: tcp

# Allow HTTPS
- action: pass
  destination_port: "443"
  protocol: tcp

First-Time Setup

1. Access Web Interface

https://zoneminder-ip/zm

2. Change Default Password

Critical security step:

  1. Log in: admin / admin
  2. Options → Users → admin
  3. Change password
  4. Save

3. Configure Storage

  1. Options → Storage
  2. Verify default storage location: /var/cache/zoneminder/events
  3. Add additional storage if needed

4. Configure Paths

  1. Options → Paths
  2. Verify FFmpeg path: /usr/bin/ffmpeg
  3. Save

5. Add Cameras

Use zoneminder_monitors role or add manually via UI:

  1. Add Monitor
  2. Enter camera details (name, RTSP URL, resolution)
  3. Save

Database Schema

Key tables:

TablePurpose
MonitorsCamera definitions
EventsRecorded events
FramesFrame metadata
ZonesMotion detection zones
StatesSystem recording states
ConfigSystem configuration
UsersUser accounts

Schema file: /usr/share/zoneminder/db/zm_create.sql

Storage Considerations

Disk space requirements:

Per camera estimation:

  • Resolution: 1920x1080 (1080p)
  • FPS: 5
  • Recording: Motion only (50% of time)
  • Retention: 30 days

Calculation:

Storage = Resolution × FPS × Recording% × Retention × Cameras
        ≈ 5GB/day × 30 days × 1 camera
        ≈ 150GB per camera

For 4 cameras: ~600GB minimum

Recommendations:

  • 1TB disk for 4-5 cameras
  • 2TB disk for 8-10 cameras
  • Use dedicated disk for /var/cache/zoneminder/

Security Considerations

  • Change Default Password: IMMEDIATELY after install
  • SSL Required: Use HTTPS in production
  • Database Password: Store in Ansible Vault
  • File Permissions: Config file is 0640 (not world-readable)
  • Network Access: Restrict web interface to trusted networks
  • Camera Credentials: Use strong passwords for camera RTSP
  • Regular Updates: Keep ZoneMinder and dependencies updated
  • Backup Database: Regular backups of MariaDB

Tags

This role does not define any tags. Use playbook-level tags if needed:

- hosts: zoneminder
  roles:
    - zoneminder_install
  tags:
    - zoneminder
    - nvr
    - surveillance
    - install

Notes

  • Role runs on target system (not localhost)
  • become: true required for package installation and config
  • SSL certificates must exist before running role
  • Database schema imported automatically on first run
  • Services enabled and started automatically
  • Compatible with Debian/Ubuntu systems
  • Idempotent: Safe to run multiple times

Troubleshooting

ZoneMinder web interface shows database error

Symptom: “Can’t connect to database” error

Check database:

# MariaDB running?
systemctl status mariadb

# Database exists?
mysql -u root -e "SHOW DATABASES LIKE 'zm';"

# User has permissions?
mysql -u root -e "SHOW GRANTS FOR 'zmuser'@'localhost';"

Test connection:

mysql -u zmuser -p zm
# Enter password from vault
# Should connect successfully

Fix:

# Restart MariaDB
systemctl restart mariadb

# Re-import schema if needed
mysql -u root zm < /usr/share/zoneminder/db/zm_create.sql

Apache shows 403 Forbidden

Symptom: Can’t access /zm URL

Check Apache config:

# ZoneMinder config enabled?
ls -la /etc/apache2/conf-enabled/ | grep zoneminder

# Apache config valid?
apachectl configtest

# Should show: "Syntax OK"

Check permissions:

# Web directory readable?
ls -la /usr/share/zoneminder/www/

# Should be owned by root:root, readable by all

Fix:

# Enable config
a2enconf zoneminder

# Restart Apache
systemctl restart apache2

ZoneMinder service won’t start

Check service status:

systemctl status zoneminder
journalctl -u zoneminder -n 50

Common issues:

  • Database not accessible
  • Config file syntax error
  • Disk space full

Check disk space:

df -h /var/cache/zoneminder/

Check config syntax:

# Check zm.conf
cat /etc/zm/zm.conf | grep -v "^#" | grep -v "^$"

Fix:

# Restart service
systemctl restart zoneminder

# Check logs
tail -f /var/log/zm/zmpkg.log

SSL certificate errors

Symptom: HTTPS not working, certificate errors

Check certificates exist:

ls -la /etc/ssl/certs/zoneminder.pem
ls -la /etc/ssl/private/zoneminder.key

Generate self-signed:

openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout /etc/ssl/private/zoneminder.key \
  -out /etc/ssl/certs/zoneminder.pem \
  -subj "/CN=$(hostname -f)"

# Restart Apache
systemctl restart apache2

High CPU usage

Cause: Too many cameras or high FPS

Solutions:

  1. Reduce FPS: Lower capture rate (5 FPS sufficient for most)
  2. Use motion detection: Don’t record continuously
  3. Reduce resolution: Use 720p instead of 1080p
  4. Hardware acceleration: Enable GPU decoding if available

Testing the Role

Verify Installation

# Check packages installed
dpkg -l | grep -E 'apache2|mariadb-server|zoneminder'

# Check services running
systemctl status apache2
systemctl status mariadb
systemctl status zoneminder

Test Database

# Connect to database
mysql -u zmuser -p zm

# List tables
SHOW TABLES;

# Check Config table exists
SELECT COUNT(*) FROM Config;

Test Web Access

# HTTP
curl -I http://localhost/zm

# Should return 200 OK

# HTTPS
curl -Ik https://localhost/zm

# Should return 200 OK

Test API

# Get API version
curl -k https://localhost/zm/api/host/getVersion.json

# Should return JSON with version info

Post-Installation Steps

  1. Change admin password (critical)
  2. Configure storage locations
  3. Set up email notifications (optional)
  4. Add cameras (via zoneminder_monitors role or UI)
  5. Configure motion detection zones
  6. Set recording schedules
  7. Configure retention policies
  8. Set up backups

Performance Tuning

For Low-Power Systems

# In ZoneMinder web interface:
# Options → System
- MAX_FPS: 5
- ALARM_FRAME_COUNT: 3

# Per monitor:
- Reduce resolution (720p)
- Lower FPS (5)
- Use passthrough recording (no re-encoding)

For High-Performance Systems

# Enable more concurrent captures
# Options → System
- MAX_CAPTURE: 8
- MAX_ANALYSIS: 4

# Use GPU acceleration
# Install: ffmpeg with NVENC/VAAPI support

Best Practices

  1. Change default password immediately: Default is admin/admin
  2. Use SSL in production: HTTP transmits credentials in plain text
  3. Regular backups: Backup MariaDB database and event recordings
  4. Monitor disk space: Set up alerts for low space
  5. Limit retention: Don’t keep events forever
  6. Use motion detection: Continuous recording uses massive space
  7. Secure camera credentials: Use strong passwords for RTSP
  8. Network segmentation: Put cameras on isolated VLAN
  9. Regular updates: Keep ZoneMinder updated for security
  10. Document configuration: Keep inventory of cameras and settings

This role is often used with:

  • zoneminder_monitors: Add cameras after installation
  • SSL certificate roles: Deploy/manage SSL certificates
  • backup_zoneminder: Backup database and recordings
  • firewall roles: Configure access to ZoneMinder

Backup Strategy

What to backup:

  1. MariaDB database: /var/lib/mysql/zm/
  2. Config file: /etc/zm/zm.conf
  3. Event recordings: /var/cache/zoneminder/events/
  4. Apache config: /etc/apache2/conf-available/zoneminder.conf

Backup script example:

#!/bin/bash
# Backup ZoneMinder database
mysqldump -u root zm > zm_backup_$(date +%Y%m%d).sql

# Backup config
cp /etc/zm/zm.conf /backup/zm.conf

# Backup recent events (last 7 days)
find /var/cache/zoneminder/events/ -mtime -7 -type f -exec cp --parents {} /backup/ \;

License

MIT

Author

Created for homelab infrastructure management.