Install guide

Installing

Deploy MxCentral from your workstation, keep server secrets on the mail host, then wire MxCentral into least-privilege SQL users, nginx, the root privileged helper, cron, and production checks.

Terminal window illustration
Per-server profileKeep deploy targets, server .env, SQL grants, and optional helper JSON in protected files outside the repository.
Deploy behaviormake deploy rsyncs the app, locks source to root, installs the helper and sudo policy, and runs production checks.
Database accessCreate separate MySQL users for vmail, iredadmin, amavisd, iredapd, and fail2ban.
Privileged workService file writes, DKIM, postmap, reloads, and Fail2ban actions go through the root helper, not direct PHP write ACLs.

Install steps

1. Get the code and configure the private deploy target.

Clone the repository onto your build workstation, create a protected local profile directory, then create an ignored Makefile.local. Use one profile name per mail server so each host keeps its own deploy target, environment file, SQL grants, and optional privileged-helper JSON.

paul@mxcentral.net:~$ git clone git@github.com:TerminalAddict/mxcentral-for-iRedmail.git
paul@mxcentral.net:~$ cd mxcentral-for-iRedmail
paul@mxcentral.net:~/mxcentral-for-iRedmail$ install -d -m 0700 "$HOME/.config/mxcentral"

paul@mxcentral.net:~/mxcentral-for-iRedmail$ cat > Makefile.local <<'EOF'
HOSTNAME := mail
DEPLOY_HOST := $(HOSTNAME)
DEPLOY_PATH := /opt/www/mxcentral-for-iRedmail
APP_USER := www-data
APP_GROUP := www-data
SERVER_ENV_FILE := $(HOME)/.config/mxcentral/$(HOSTNAME).env
DATABASE_GRANTS_FILE := $(HOME)/.config/mxcentral/$(HOSTNAME).sql
# PRIVILEGED_CONFIG_FILE := $(HOME)/.config/mxcentral/$(HOSTNAME)-helper.json
EOF

2. Run the deploy target.

Run make deploy from the repo root. The current deploy target creates required runtime directories, rsyncs the application, preserves host-specific files, locks application source and .env to root ownership, installs the privileged helper and single-command sudo policy, then clears Laravel caches and runs production health checks as the app user.

  • Remote .env, storage/, and /etc/mxcentral/privileged-helper.json are preserved unless explicit profile files are supplied.
  • Local database/*.sqlite*, node_modules/, and public/hot are not deployed.
  • Application code, dependencies, configuration, and .env are root-owned. Only storage/ and bootstrap/cache/ are writable by the PHP user.
  • For updating multiple existing installs, use scripts/deploy-rsync.sh. It refuses to deploy unless the remote path already looks like MxCentral and the remote account has root or passwordless sudo.
  • If an older checkout fails looking for database/database.sqlite, set the runtime stores and helper command in the server .env, then rerun make deploy.
paul@mxcentral.net:~/mxcentral-for-iRedmail$ make deploy

paul@mxcentral.net:~/mxcentral-for-iRedmail$ scripts/deploy-rsync.sh paul@mail.example.com /opt/www/mxcentral-for-iRedmail

3. Create or fix the server environment file.

Keep the remote .env on the server. Deployment deliberately preserves it unless SERVER_ENV_FILE names an explicit local profile. For a new install, start from .env.production.example, generate the Laravel key, then make the file root-owned and group-readable by the PHP worker.

  • Keep the generated APP_KEY stable. Decryptable mailbox password storage depends on it.
  • Set APP_URL=https://your-mail-host.example/mxcentral.
  • Use persistent session, cache, login rate-limit, and password-reveal cache stores that survive between HTTP requests.
  • Password reveals are disabled unless MXCENTRAL_PASSWORD_REVEAL_ADMINS allowlists a global administrator. TOTP is required by default with MXCENTRAL_PASSWORD_REVEAL_REQUIRE_TOTP=true.
  • Set MXCENTRAL_PRIVILEGED_HELPER_COMMAND="/usr/bin/sudo /usr/local/sbin/mxcentral-privileged".
  • Set MXCENTRAL_CRON_USER and MXCENTRAL_SUDO_PATH for the cron runner.
  • Set one username and password for each iRedMail schema: VMAIL, IREDADMIN, AMAVISD, IREDAPD, and FAIL2BAN.
  • Executable paths, service names, file ownership, modes, and privileged target paths belong in root-owned /etc/mxcentral/privileged-helper.json, not the PHP-readable .env.
paul@mxcentral.net:/opt/www/mxcentral-for-iRedmail$ install -o root -g www-data -m 0640 .env.production.example .env
paul@mxcentral.net:/opt/www/mxcentral-for-iRedmail$ php artisan key:generate
paul@mxcentral.net:/opt/www/mxcentral-for-iRedmail$ chown root:www-data .env
paul@mxcentral.net:/opt/www/mxcentral-for-iRedmail$ chmod 0640 .env
paul@mxcentral.net:/opt/www/mxcentral-for-iRedmail$ ${EDITOR:-vi} .env

APP_NAME="mxcentral-for-iRedmail"
APP_URL=https://your-mail-host.example/mxcentral
SESSION_DRIVER=file
CACHE_STORE=file
QUEUE_CONNECTION=sync
MXCENTRAL_PRIVILEGED_HELPER_COMMAND="/usr/bin/sudo /usr/local/sbin/mxcentral-privileged"
MXCENTRAL_LOGIN_RATE_CACHE_STORE=file
MXCENTRAL_PASSWORD_REVEAL_ADMINS=
MXCENTRAL_PASSWORD_REVEAL_REQUIRE_TOTP=true
MXCENTRAL_PASSWORD_REVEAL_TOTP_SECRETS="{}"
MXCENTRAL_PASSWORD_REVEAL_CACHE_STORE=file
MXCENTRAL_CRON_USER=www-data
MXCENTRAL_SUDO_PATH=/usr/bin/sudo

IREDMAIL_DB_HOST=127.0.0.1
IREDMAIL_DB_PORT=3306
VMAIL_DB_USERNAME=mxcentral_vmail
VMAIL_DB_PASSWORD=unique-vmail-secret
IREDADMIN_DB_USERNAME=mxcentral_iredadmin
IREDADMIN_DB_PASSWORD=unique-iredadmin-secret
AMAVISD_DB_USERNAME=mxcentral_amavisd
AMAVISD_DB_PASSWORD=unique-amavisd-secret
IREDAPD_DB_USERNAME=mxcentral_iredapd
IREDAPD_DB_PASSWORD=unique-iredapd-secret
FAIL2BAN_DB_USERNAME=mxcentral_fail2ban
FAIL2BAN_DB_PASSWORD=unique-fail2ban-secret

paul@mxcentral.net:/opt/www/mxcentral-for-iRedmail$ sudo -u www-data php artisan optimize:clear

4. Create the least-privilege SQL users.

Create a protected copy of docs/database-grants.sql for each server, replace every placeholder password, and apply it manually to the matching mail host. The five passwords in the SQL profile must match the five schema-specific passwords in the server .env.

  • Do not grant MxCentral privileges on *.*.
  • Do not grant FILE, GRANT OPTION, or access to unrelated schemas.
  • The global-admin Audit Log reads iredadmin.log. Existing installs that granted insert-only access must add SELECT before deploying that feature.
  • The optional ALTER ON vmail.mailbox grant is needed only when decryptable-password schema toggling stays application-managed.
paul@mxcentral.net:~/mxcentral-for-iRedmail$ cp mxcentral-for-iRedmail/docs/database-grants.sql "$HOME/.config/mxcentral/mail.sql"
paul@mxcentral.net:~/mxcentral-for-iRedmail$ chmod 0600 "$HOME/.config/mxcentral/mail.sql"
paul@mxcentral.net:~/mxcentral-for-iRedmail$ ${EDITOR:-vi} "$HOME/.config/mxcentral/mail.sql"

-- excerpt from docs/database-grants.sql; keep the full table-level template.
CREATE USER IF NOT EXISTS 'mxcentral_vmail'@'localhost' IDENTIFIED BY 'CHANGE_ME_VMAIL';
CREATE USER IF NOT EXISTS 'mxcentral_iredadmin'@'localhost' IDENTIFIED BY 'CHANGE_ME_IREDADMIN';
CREATE USER IF NOT EXISTS 'mxcentral_amavisd'@'localhost' IDENTIFIED BY 'CHANGE_ME_AMAVISD';
CREATE USER IF NOT EXISTS 'mxcentral_iredapd'@'localhost' IDENTIFIED BY 'CHANGE_ME_IREDAPD';
CREATE USER IF NOT EXISTS 'mxcentral_fail2ban'@'localhost' IDENTIFIED BY 'CHANGE_ME_FAIL2BAN';

GRANT SELECT, INSERT, UPDATE, DELETE ON vmail.mailbox TO 'mxcentral_vmail'@'localhost';
GRANT SELECT, INSERT ON iredadmin.log TO 'mxcentral_iredadmin'@'localhost';
GRANT SELECT, DELETE ON amavisd.quarantine TO 'mxcentral_amavisd'@'localhost';
GRANT SELECT, INSERT, UPDATE ON iredapd.throttle TO 'mxcentral_iredapd'@'localhost';
GRANT SELECT, UPDATE ON fail2ban.banned TO 'mxcentral_fail2ban'@'localhost';

-- existing insert-only deployments can apply just:
-- GRANT SELECT, INSERT ON iredadmin.log TO 'mxcentral_iredadmin'@'localhost';
-- FLUSH PRIVILEGES;

paul@mxcentral.net:~/mxcentral-for-iRedmail$ ssh mail 'sudo -n mysql' < "$HOME/.config/mxcentral/mail.sql"

5. Install the privileged helper and sudoers include.

The deploy script installs these automatically. For a manual install, place the privileged helper at the fixed root-owned path, install the root-owned helper JSON, validate the sudoers include, and install it under a dotless filename. Do not grant the PHP user direct write ACLs on /var/lib/dkim, Postfix, Amavis, iRedAPD, or SOGo files.

  • The sudo policy grants only /usr/local/sbin/mxcentral-privileged with no command-line arguments.
  • The helper reads one JSON request on stdin, validates named operations, rejects unsafe links, writes with O_NOFOLLOW, fsyncs, and atomically renames files.
  • DKIM creation, ownership fixes, service reloads, postmap, and Fail2ban unban are all brokered through the helper.
  • Set web_users and any non-default service paths in /etc/mxcentral/privileged-helper.json.
paul@mxcentral.net:/opt/www/mxcentral-for-iRedmail$ install -o root -g root -m 0755 scripts/mxcentral-privileged /usr/local/sbin/mxcentral-privileged
paul@mxcentral.net:/opt/www/mxcentral-for-iRedmail$ install -d -o root -g root -m 0755 /etc/mxcentral
paul@mxcentral.net:/opt/www/mxcentral-for-iRedmail$ install -o root -g root -m 0640 docs/privileged-helper.json /etc/mxcentral/privileged-helper.json
paul@mxcentral.net:/opt/www/mxcentral-for-iRedmail$ visudo -cf docs/sudoers.conf
paul@mxcentral.net:/opt/www/mxcentral-for-iRedmail$ install -o root -g root -m 0440 docs/sudoers.conf /etc/sudoers.d/mxcentral-for-iRedmail

6. Mount the app in nginx.

Configure nginx to route /mxcentral to /opt/www/mxcentral-for-iRedmail/public. PHP must run through PHP-FPM and Laravel must receive requests through public/index.php. Render the provided location template for the deployed public path, include it before broad PHP catchall rules, then test and reload nginx.

  • The provided file contains location blocks.
  • Do not install it directly as /etc/nginx/sites-available/mxcentral.conf unless it is wrapped inside a valid server block.
paul@mxcentral.net:/opt/www/mxcentral-for-iRedmail$ scripts/render-nginx-template.sh \
  /opt/www/mxcentral-for-iRedmail/public \
  /etc/nginx/templates/mxcentral.tmpl

paul@mxcentral.net:/opt/www/mxcentral-for-iRedmail$ ${EDITOR:-vi} /etc/nginx/sites-enabled/00-default-ssl.conf

include /etc/nginx/templates/mxcentral.tmpl;

paul@mxcentral.net:/opt/www/mxcentral-for-iRedmail$ nginx -t
paul@mxcentral.net:/opt/www/mxcentral-for-iRedmail$ systemctl reload nginx

7. Add cron and check scheduled tasks.

The deploy target clears Laravel caches, but run optimize:clear again after editing .env. Add one cron entry for all MxCentral scheduled tasks. If the cron runner is invoked as root, it refuses to launch Artisan until MXCENTRAL_CRON_USER names an existing non-root application user.

paul@mxcentral.net:/opt/www/mxcentral-for-iRedmail$ sudo -u www-data php artisan optimize:clear

* * * * * MXCENTRAL_CRON_USER=www-data MXCENTRAL_SUDO_PATH=/usr/bin/sudo /usr/bin/php /opt/www/mxcentral-for-iRedmail/bin/cron.php >/dev/null 2>&1

paul@mxcentral.net:/opt/www/mxcentral-for-iRedmail$ /usr/bin/php /opt/www/mxcentral-for-iRedmail/bin/cron.php --list
paul@mxcentral.net:/opt/www/mxcentral-for-iRedmail$ /usr/bin/php /opt/www/mxcentral-for-iRedmail/bin/cron.php --task=quarantine-notifications --force

8. Run post-install checks.

Confirm Laravel can clear caches, pass the production health check, read routes, and run the quarantine notification dry run. Then verify the PHP worker cannot write application code, .env, or service configuration directly. Privileged changes should only pass through the root helper.

paul@mxcentral.net:/opt/www/mxcentral-for-iRedmail$ sudo -u www-data php artisan optimize:clear
paul@mxcentral.net:/opt/www/mxcentral-for-iRedmail$ sudo -u www-data php artisan mxcentral:check-production
paul@mxcentral.net:/opt/www/mxcentral-for-iRedmail$ sudo -u www-data php artisan route:list >/dev/null
paul@mxcentral.net:/opt/www/mxcentral-for-iRedmail$ sudo -u www-data php artisan quarantine:notify-recipients --dry-run

paul@mxcentral.net:/opt/www/mxcentral-for-iRedmail$ sudo -u www-data test ! -w app
paul@mxcentral.net:/opt/www/mxcentral-for-iRedmail$ sudo -u www-data test ! -w .env
paul@mxcentral.net:/opt/www/mxcentral-for-iRedmail$ sudo -u www-data test ! -w /opt/iredapd/settings.py
paul@mxcentral.net:/opt/www/mxcentral-for-iRedmail$ sudo -u www-data test ! -w /etc/amavis/conf.d/50-user
paul@mxcentral.net:/opt/www/mxcentral-for-iRedmail$ sudo -u www-data test ! -w /etc/postfix/main.cf
Main thing to watch

Do not widen access to recover convenience. The current deployment expects root-owned application code, a root-owned protected .env, table-level MySQL grants, and privileged service changes brokered through /usr/local/sbin/mxcentral-privileged.