Contents
- Scope and assumptions
- VPS sizing by subscriber count
- OS preparation and hardening
- PHP installation, extensions, tuning
- Database configuration
- Web server: Nginx vs Apache
- MailWizz installation and license
- The cron job set MailWizz needs
- Delivery server configuration
- Authentication setup
- Post-install hardening
- Pre-launch verification tests
Scope and assumptions
This checklist covers a production MailWizz deployment on a Linux VPS with operator-grade reliability and security expectations. Target operator: has purchased MailWizz licensing ($86 Regular or $295 Extended), has root access to a fresh VPS, plans to use MailWizz for legitimate email marketing or as a multi-tenant ESP front-end, and wants a sequenced setup rather than a tutorial. If you are evaluating whether MailWizz fits your needs, this is not the right document; if you have decided on MailWizz and need to deploy it correctly the first time, continue.
The recommended baseline stack as of 2026: Ubuntu 22.04 LTS or Debian 12, Nginx 1.24+ as web server, PHP 8.1+ (MailWizz officially requires 7.2+ but 8.1 performs substantially better), MariaDB 10.6+ or MySQL 8.0+ with InnoDB, Redis 7.0+ for caching, Let's Encrypt for SSL, systemd for service management. The procedure also works with Apache 2.4 and earlier PHP/MariaDB versions but the recommended baseline reflects current best practice.
VPS sizing by subscriber count
| Subscribers | vCPU | RAM | Storage | Monthly cost |
|---|---|---|---|---|
| Under 50K | 2 | 4 GB | 80 GB SSD | $20-30 |
| 50K-250K | 4 | 8 GB | 160 GB SSD | $40-60 |
| 250K-1M | 8 | 16 GB | 320 GB SSD | $80-120 |
| 1M-5M | 8x2 servers | 16 GB each | 320 GB SSD each | $200-400 |
| 5M+ | Split tiers | 32 GB+ per tier | NVMe | $500+ |
Choose providers with good network presence and reasonable IP reputation: Hetzner, OVH, Vultr, DigitalOcean. Avoid Amazon EC2 default IPs unless paired with SES or external delivery service. Storage must be SSD or NVMe; HDD destroys MailWizz performance even at small scale due to MySQL I/O patterns during list operations. The most common sizing mistake is under-specifying RAM; choose memory over CPU when budget-constrained.
OS preparation and hardening
Before installing MailWizz, harden the base OS:
- Update all packages:
apt update && apt upgrade -y - Configure timezone:
timedatectl set-timezone UTC(correct for any multi-region deployment) - Install fail2ban:
apt install fail2banand enable SSH protection profile - Configure ufw firewall: allow 22 (SSH), 80 (HTTP), 443 (HTTPS); deny everything else
- Disable root SSH login: edit /etc/ssh/sshd_config, set PermitRootLogin no, restart sshd
- Create dedicated user for administration: adduser mailwizz-admin, add to sudo group
- Install SSH key for the dedicated user, disable password authentication
- Set automatic security updates: apt install unattended-upgrades
- Configure swap if RAM under 8 GB: 2 GB swap file with swappiness=10
- Install monitoring agent (Netdata, Prometheus node_exporter) before MailWizz so baseline metrics exist
These steps are basic Linux hosting hygiene but operators frequently skip them in the rush to install MailWizz. The result is production MailWizz instances exposed unnecessarily to SSH brute-force attacks and missing baseline monitoring data needed to debug later issues.
PHP installation, extensions, tuning
Install PHP 8.1 with all extensions MailWizz needs:
add-apt-repository ppa:ondrej/php
apt update
apt install php8.1-fpm php8.1-cli php8.1-common \
php8.1-mysql php8.1-curl php8.1-gd php8.1-mbstring \
php8.1-xml php8.1-zip php8.1-imap php8.1-bcmath \
php8.1-gmp php8.1-intl php8.1-opcache php8.1-redis \
php8.1-soap php8.1-imagick
Configure PHP for MailWizz workloads. Edit /etc/php/8.1/fpm/php.ini and /etc/php/8.1/cli/php.ini:
| Directive | Recommended value | Why |
|---|---|---|
memory_limit | 512M (cli: 1G) | Default 128M too low for list imports |
max_execution_time | 300 (cli: 0) | Long imports need time |
upload_max_filesize | 64M | List import CSV uploads |
post_max_size | 64M | Match upload_max_filesize |
date.timezone | UTC | Avoid timezone-related cron drift |
opcache.enable | 1 | 3-5x PHP performance improvement |
opcache.memory_consumption | 256 | MailWizz codebase is substantial |
opcache.max_accelerated_files | 20000 | Cover all MailWizz PHP files |
Verify PHP installation with php -m showing all required extensions: gd, imap, zip, mbstring, curl, gmp, intl, opcache, mysqli, pdo_mysql, redis. Missing any of these blocks MailWizz installation or breaks specific features.
Database configuration
Install MariaDB 10.6 and configure for MailWizz:
apt install mariadb-server mariadb-client
mysql_secure_installation
Create database and user:
mysql -u root -p
CREATE DATABASE mailwizz CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'mailwizz'@'localhost' IDENTIFIED BY 'STRONG_PASSWORD_HERE';
GRANT ALL PRIVILEGES ON mailwizz.* TO 'mailwizz'@'localhost';
FLUSH PRIVILEGES;
Tune MariaDB for MailWizz workloads. Edit /etc/mysql/mariadb.conf.d/50-server.cnf:
| Directive | 8 GB RAM | 16 GB RAM |
|---|---|---|
innodb_buffer_pool_size | 3G | 8G |
innodb_log_file_size | 512M | 1G |
innodb_flush_log_at_trx_commit | 2 | 2 |
innodb_file_per_table | 1 | 1 |
max_connections | 200 | 500 |
character-set-server | utf8mb4 | utf8mb4 |
collation-server | utf8mb4_unicode_ci | utf8mb4_unicode_ci |
Restart MariaDB after configuration changes: systemctl restart mariadb. Verify the changes took effect with mysql -e "SHOW VARIABLES LIKE 'innodb_buffer_pool_size'".
Web server: Nginx vs Apache
Both work. The 2026 recommendation is Nginx for new deployments because Nginx handles static assets and HTTPS termination more efficiently than Apache for typical MailWizz workloads. Apache works if your team has existing Apache expertise or specific .htaccess-based configuration requirements.
Nginx configuration for MailWizz:
server {
listen 80;
server_name mailwizz.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name mailwizz.example.com;
root /var/www/mailwizz;
index index.php;
ssl_certificate /etc/letsencrypt/live/mailwizz.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mailwizz.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
client_max_body_size 64M;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_read_timeout 300;
}
location ~ /\.ht {
deny all;
}
}
MailWizz installation and license
Download and extract MailWizz:
cd /var/www
unzip mailwizz-installer.zip -d mailwizz
chown -R www-data:www-data mailwizz
chmod -R 755 mailwizz
chmod -R 775 mailwizz/apps/common/runtime
chmod -R 775 mailwizz/apps/common/views/cache
chmod -R 775 mailwizz/frontend/assets/cache
chmod -R 775 mailwizz/backend/assets/cache
chmod -R 775 mailwizz/customer/assets/cache
Navigate to https://mailwizz.example.com/install/ in browser. The installer steps through:
- License agreement
- Requirements check (all PHP extensions must show green)
- License key entry (from MailWizz purchase confirmation email)
- Database configuration (use the credentials from MariaDB setup above)
- Database import (MailWizz creates all required tables)
- Admin account creation (first admin user)
- Cron job display (the installer shows the exact cron commands to add)
- Installation complete (the installer prompts to delete the /install directory)
The license key activation requires internet connectivity from the MailWizz server outbound to api.mailwizz.com for license verification. Firewall rules must allow this; corporate firewalls sometimes block it and produce confusing "license invalid" errors that are actually network connectivity issues.
The most critical post-install task: delete the /var/www/mailwizz/install directory immediately after installation completes. Leaving it in place means anyone who reaches the install URL can reinitialize the database and wipe production data. The MailWizz installer prompts to delete it but operators frequently dismiss the prompt and forget. Add to your install runbook: rm -rf /var/www/mailwizz/install as final step before launch.
The cron job set MailWizz needs
MailWizz requires Linux cron jobs (not web crons through external services) because the heavy jobs need sustained execution beyond HTTP timeout limits. Add to crontab as the web server user:
# Edit crontab as www-data
sudo -u www-data crontab -e
# Add the following lines:
* * * * * /usr/bin/php -q /var/www/mailwizz/apps/console/console.php send-campaigns >/dev/null 2>&1
* * * * * /usr/bin/php -q /var/www/mailwizz/apps/console/console.php send-transactional-emails >/dev/null 2>&1
*/10 * * * * /usr/bin/php -q /var/www/mailwizz/apps/console/console.php bounce-handler >/dev/null 2>&1
*/10 * * * * /usr/bin/php -q /var/www/mailwizz/apps/console/console.php feedback-loop-handler >/dev/null 2>&1
*/10 * * * * /usr/bin/php -q /var/www/mailwizz/apps/console/console.php process-email-box-monitors >/dev/null 2>&1
0 0 * * * /usr/bin/php -q /var/www/mailwizz/apps/console/console.php daily-process-subscribers >/dev/null 2>&1
0 * * * * /usr/bin/php -q /var/www/mailwizz/apps/console/console.php hourly >/dev/null 2>&1
0 0 * * * /usr/bin/php -q /var/www/mailwizz/apps/console/console.php daily >/dev/null 2>&1
Verify cron jobs are running by checking logs at /var/www/mailwizz/apps/common/runtime/console.log for recent activity. The send-campaigns log should show timestamps every minute when campaigns are processing.
Delivery server configuration
MailWizz needs at least one delivery server configured before campaigns can send. Common patterns:
Amazon SES. The cheapest option at scale. Configure in MailWizz under Servers then Delivery Servers then Add then Amazon SES (Web API). Provide AWS access key, secret key, region. Verify sending domain in SES console before MailWizz can send (SES requires domain verification with DKIM records).
SMTP relay. Use any SMTP server (your own Postfix, external service like SendGrid SMTP, Mailgun SMTP). Configure with hostname, port (typically 587 with TLS), username, password, encryption. The most flexible option but requires the SMTP provider already set up.
PowerMTA or KumoMTA. Configure as SMTP delivery server pointing to your MTA's listener address. Pair with proper outbound IP and authentication setup. This is the standard pattern for ESPs running their own MTA infrastructure.
SendGrid Web API. Configure with SendGrid API key for HTTP-based sending. Better throughput than SMTP for some workloads but ties you to SendGrid specifically.
Mailgun Web API. Similar to SendGrid Web API pattern.
SparkPost Web API. Similar pattern.
Multiple delivery servers can be configured simultaneously for load balancing or campaign-specific routing. MailWizz supports per-customer delivery server assignment in multi-tenant deployments, which is the key economic advantage of MailWizz versus per-customer SaaS pricing.
Authentication setup
Before sending any production email, configure authentication for every sending domain. This applies regardless of delivery server choice.
SPF. Add TXT record at the root of sending domain authorizing the IPs or services that send on its behalf. Example for SES: v=spf1 include:amazonses.com -all. Example for SMTP relay: v=spf1 ip4:203.0.113.42 -all. The -all (hard fail) is preferred over ~all (soft fail) for production senders.
DKIM. Generate or retrieve DKIM keys from your delivery service, add the public key as TXT record at selector._domainkey.yourdomain.com. The selector name varies by service. MailWizz also has built-in DKIM signing for SMTP delivery servers configured under Sending Domains.
DMARC. Add TXT record at _dmarc.yourdomain.com. Start with v=DMARC1; p=none; rua=mailto:dmarc@yourdomain.com; to monitor without blocking, then progress to p=quarantine and eventually p=reject as alignment is verified. DMARC is mandatory for Gmail and Yahoo bulk sender compliance since February 2024.
rDNS. Configure reverse DNS for your outbound IPs to resolve to a hostname matching the EHLO domain. Most VPS providers expose rDNS configuration through their control panel.
List-Unsubscribe. MailWizz generates RFC 8058 one-click List-Unsubscribe headers automatically for marketing campaigns. Verify they appear in test sends. Also required by Gmail and Yahoo bulk sender compliance.
Post-install hardening
After installation completes and before launching campaigns:
- Delete /install directory:
rm -rf /var/www/mailwizz/install - Change default admin URL by configuring custom backend path in apps/backend/config/main-custom.php
- Lock down /apps/backend with IP allowlisting at Nginx level for admin URL access
- Enable two-factor authentication for admin accounts under user profile
- Set strong password policy in backend Settings then Common then Security
- Configure session timeout to 30-60 minutes maximum
- Disable PHP exposure:
expose_php = Offin php.ini - Configure Nginx to remove Server header:
server_tokens off;in nginx.conf - Set up daily database backups to off-server storage (S3, Backblaze B2, or similar)
- Configure log rotation for MailWizz logs (logrotate config for apps/common/runtime)
- Install Redis caching:
apt install redis-serverthen configure in MailWizz backend - Install Let's Encrypt SSL:
certbot --nginx -d mailwizz.example.com
Pre-launch verification tests
Before sending any production campaign:
- Create test list with 5-10 internal addresses you control (Gmail, Outlook, Yahoo, ProtonMail, iCloud)
- Configure sending domain with full SPF/DKIM/DMARC
- Send test campaign to the test list
- Verify SpamAssassin score using mail-tester.com or similar tool (target under 1.0)
- Verify the email arrives in inbox (not spam) at all major providers
- Verify tracking pixel records open in MailWizz analytics
- Verify tracked link records click in MailWizz analytics
- Verify unsubscribe link works and removes subscriber from list
- Verify bounce handling by sending to a non-existent address and checking bounce processing
- Verify cron jobs by checking console.log shows recent activity for all jobs
- Verify Redis cache by checking
redis-cli info statsshows keyspace_hits incrementing - Verify SSL certificate by checking https URL via SSL Labs gets at least A grade
- Verify outbound IP reputation at senderscore.org and Talos Intelligence
- Verify DMARC alignment by checking received DMARC reports
A pre-launch test we always recommend: send a test campaign with the full message you intend to send to production, but to only your internal test list. Check the actual rendered email at every major provider (Gmail web, Gmail iOS, Outlook web, Outlook desktop, Yahoo, iCloud) for: rendering correctness, inbox placement, tracking pixel firing, click tracking, unsubscribe link function, List-Unsubscribe header presence, From and Reply-To addresses matching expectations, SPF/DKIM/DMARC alignment in raw headers. This 15-minute test catches the substantial majority of configuration problems before they affect real subscribers. Operators who skip this step and send straight to production lists discover problems in the most expensive way possible. The pattern: configure thoroughly, test exhaustively to your own addresses, then launch to real lists with confidence.
This checklist covers the substantive setup work for a MailWizz production deployment. The procedure is methodical rather than glamorous and operators who follow it sequentially produce reliable installations that handle their workload without surprises. The skipped steps are where the operational pain accumulates: missing Redis caching produces slow customer experience; missing post-install hardening produces security incidents; missing pre-launch verification produces deliverability incidents. The investment in doing this checklist properly pays back many times over the lifetime of the deployment.