Install and maintain Enterprise Email on Ubuntu
Enterprise Email combines Nextcloud 32.0.2 with Apache, MariaDB, Postfix, and Dovecot virtual mailboxes, using Nextcloud users for mailbox authentication.
Before you start
- Use a clean, supported Ubuntu server with root or sudo access.
- Point
app.example.comto the server before requesting a public TLS certificate. - Replace every value written as
CHANGE_MEand store the generated credentials in a password manager. - Take a snapshot before changing an existing installation.
- DNS for
inbox.app.example.com, plus correct MX, SPF, DKIM, DMARC, reverse-DNS, and outbound-mail policy for your environment. - A deliberate storage plan for Nextcloud data and virtual mailboxes.
- A relay host only if your mail provider requires one; do not reuse the playbook environment’s relay address blindly.
What the AppStore installation creates
- Nextcloud 32.0.2 below
/var/www/app.example.com/nextcloud - MariaDB database used by Nextcloud and read by Postfix/Dovecot SQL maps
- Postfix SMTP and Dovecot IMAP with virtual mail user/group ID 5000
- Optional XFS data mount at
/mnt/datawith user and project quotas - Apache TLS virtual host, UFW, and Fail2ban
1. Prepare storage safely
Skip this step if you are using the root filesystem. The following is an example only; replace /dev/EMPTY_DATA_DISK after verifying it is the intended unused device.
lsblk -f
findmnt
sudo wipefs --no-act /dev/EMPTY_DATA_DISK
# DESTRUCTIVE when run against the wrong device:
sudo mkfs.xfs /dev/EMPTY_DATA_DISK
sudo install -d -m 0755 /mnt/data
sudo mount -o defaults,noatime,nodiratime,uquota,pquota /dev/EMPTY_DATA_DISK /mnt/data
findmnt /mnt/data
2. Install the web, database, and mail stack
Preseed Postfix as an Internet Site using your mail FQDN.
sudo apt update
sudo apt install -y apache2 mariadb-server postfix postfix-mysql \
dovecot-core dovecot-imapd dovecot-lmtpd dovecot-mysql \
php php-cli php-common php-mysql php-curl php-gd php-intl php-mbstring \
php-xml php-zip php-bcmath php-gmp php-imagick libapache2-mod-php \
unzip curl certbot python3-certbot-apache ufw fail2ban
sudo systemctl enable --now mariadb apache2 postfix dovecot
3. Create the Nextcloud database and install Nextcloud
Use a database name derived from the domain only after replacing punctuation safely.
sudo mysql
CREATE DATABASE enterprise_email CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'enterprise_email'@'localhost' IDENTIFIED BY 'CHANGE_ME_DB_PASSWORD';
GRANT ALL PRIVILEGES ON enterprise_email.* TO 'enterprise_email'@'localhost';
FLUSH PRIVILEGES;
EXIT;
sudo install -d -o www-data -g www-data -m 0750 /var/www/app.example.com
curl -fL https://download.nextcloud.com/server/releases/nextcloud-32.0.2.zip -o /tmp/nextcloud.zip
sudo unzip /tmp/nextcloud.zip -d /var/www/app.example.com
sudo install -d -o www-data -g www-data -m 0770 /mnt/data/nextcloud/app.example.com
sudo chown -R www-data:www-data /var/www/app.example.com/nextcloud
4. Complete Nextcloud and Apache setup
Create the Apache site for /var/www/app.example.com/nextcloud, enable rewrite/headers/env/dir/mime/ssl, issue TLS, then install Nextcloud with occ maintenance:install or its web installer.
sudo a2enmod rewrite headers env dir mime ssl
sudo apache2ctl configtest
sudo systemctl reload apache2
sudo certbot --apache -d inbox.app.example.com -m admin@example.com --agree-tos --redirect
sudo -u www-data php /var/www/app.example.com/nextcloud/occ status
5. Configure Postfix and Dovecot SQL authentication
Create SQL map files with mode 0640 and a dedicated read-only database grant where possible. Map Nextcloud user IDs to virtual mailboxes below /mnt/data/vmail, configure LMTP delivery, then validate both daemons before restarting.
sudo groupadd -g 5000 vmail
sudo useradd -g vmail -u 5000 -d /mnt/data/vmail -m -s /usr/sbin/nologin vmail
sudo postconf -n
sudo postfix check
sudo doveconf -n
sudo systemctl restart postfix dovecot
sudo doveadm auth test USER@app.example.com
Important files and data
- Nextcloud code:
/var/www/app.example.com/nextcloud - Nextcloud data:
/mnt/data/nextcloud/app.example.com - Virtual mailboxes:
/mnt/data/vmail - Postfix configuration and SQL maps:
/etc/postfix/ - Dovecot configuration and SQL maps:
/etc/dovecot/
Health checks and logs
Run these checks after installation and after each upgrade:
sudo -u www-data php /var/www/app.example.com/nextcloud/occ status
sudo postfix check
sudo doveconf -n
sudo systemctl status apache2 mariadb postfix dovecot --no-pager
sudo journalctl -u postfix -u dovecot -n 100 --no-pager
Routine maintenance
Review release notes and take a backup or snapshot before upgrading. Use the following playbook-aligned commands as the starting point:
sudo -u www-data php /var/www/app.example.com/nextcloud/occ maintenance:mode --on
# Back up database, config, data, and mail before upgrading.
sudo -u www-data php /var/www/app.example.com/nextcloud/occ maintenance:mode --off
sudo newaliases
sudo systemctl reload postfix dovecot apache2
Backup scope
- Nextcloud MariaDB dump,
config/, apps, and data directory - All virtual mailboxes and quota metadata
/etc/postfix,/etc/dovecot, Apache configuration, TLS material, and DNS records
A usable backup needs both application files and application data. Test restoration on a separate server; an untested backup is not a recovery plan.
Troubleshooting
- Confirm DNS with
dig +short app.example.combefore retrying Certbot. - Test the web-server configuration before reloading it:
sudo nginx -torsudo apache2ctl configtest. - Check free space with
df -hand listening ports withsudo ss -ltnup. - If a service fails, inspect its systemd journal before changing configuration.
- Test SMTP and IMAP locally before debugging public DNS or provider port filtering.
- If mail authentication breaks after a Nextcloud update, validate the SQL query against the current user schema without logging password hashes.
- If quota reporting is wrong, confirm the XFS mount options are active with
findmnt -no OPTIONS /mnt/data.
Security notes
- Do not paste passwords, API keys, repository credentials, private keys, or access tokens into tickets or public logs.
- Expose only the documented public ports. Keep database and application backend ports bound to localhost or a private network.
- Keep SSH access working before enabling UFW, then allow only the ports this guide lists.
- Renewal can be tested safely with
sudo certbot renew --dry-runwhere Certbot manages TLS.

