Installation
This guide installs modoboa-pro into an existing Modoboa instance. All commands assume you have shell access to the server and can activate the Python virtual environment Modoboa runs in.
INFO
Throughout this page, <modoboa_instance> is the directory that contains your instance's manage.py and settings module (often /srv/modoboa/instance), and <venv> is the Python virtual environment Modoboa runs in.
1. Install the package
modoboa-pro is distributed from the Modoboa private package index (packages.modoboa.org), not from public PyPI. Install it into the same virtual environment as Modoboa so it lands on the same PYTHONPATH, passing the private index with the credentials issued to your account:
$ <venv>/bin/pip install \
--extra-index-url https://<user>:<password>@packages.modoboa.org/pypi/modo-pypi/simple/ \
modoboa-proThis pulls in the acme, josepy and cryptography dependencies described in Requirements. They come from public PyPI — the private index only hosts modoboa-pro itself.
WARNING
Use --extra-index-url, not --index-url. --extra-index-url adds the private index alongside PyPI, so the public dependencies (acme, cryptography, django…) still resolve. --index-url replaces PyPI, and the install then fails with No matching distribution found for acme (or cryptography) because the private index does not carry those packages.
To avoid leaking the credentials in your shell history (and to make later upgrades a plain pip install -U modoboa-pro), store the private index in pip's configuration instead. Create the venv's pip.conf:
# <venv>/pip.conf
[global]
index-url = https://pypi.org/simple
extra-index-url = https://<user>:<password>@packages.modoboa.org/pypi/modo-pypi/simple/INFO
pip reads this file automatically when that virtual environment is active. You can also use the user-wide ~/.config/pip/pip.conf (Linux) or set the PIP_EXTRA_INDEX_URL environment variable. To keep the password out of the file entirely, drop <user>:<password>@ from the URL and add a matching entry to ~/.netrc (machine packages.modoboa.org login <user> password <password>, mode 600).
With that in place the install (and any future upgrade) is simply:
$ <venv>/bin/pip install modoboa-proTIP
The package ships the prebuilt frontend assets, so you do not need Node.js or a frontend build step on the server.
2. Register the apps
Open your instance's settings module (<modoboa_instance>/settings.py) and add the two plugin apps to the MODOBOA_APPS tuple, after the core entries:
MODOBOA_APPS = (
"modoboa",
"modoboa.core",
"modoboa.lib",
"modoboa.admin",
# ... your other Modoboa apps ...
# Modoboa Pro
"modoboa_pro",
"modoboa_pro.licensing",
"modoboa_pro.virtualhosts",
)modoboa_pro registers the extension with Modoboa's extension pool (so the Virtual hosts menu entry and the frontend remote appear), modoboa_pro.licensing provides the installation registration that unlocks the plugin (see the next step), and modoboa_pro.virtualhosts provides the Django app — models, migrations, REST API, signal handlers and background jobs.
3. Configure your license key
modoboa-pro is gated behind an installation registration. Until the install is registered, every plugin API endpoint returns 403 {"code": "license_required"}, all background jobs (provisioning, certificate renewals) are no-ops, and the frontend hides the features behind an activation screen.
Add the license key issued to your account to the same settings.py:
# Modoboa Pro licensing
MODOBOA_PRO_LICENSE_KEY = "<key issued to your account>"The key is read from settings, never stored: only a SHA-256 hash of it is persisted (to detect a key swap). Registration phones home once with {key, instance_id, hostname, version} to validate the install; afterwards the plugin works offline.
INFO
The licensing endpoint defaults to the production server. To point at a staging or self-hosted licensing server, set the optional MODOBOA_PRO_LICENSE_API_URL:
MODOBOA_PRO_LICENSE_API_URL = "https://modoboa.com/api/plugin/register/"Registration happens lazily the first time the activation screen loads, but you can trigger it explicitly (and confirm the key is valid) once migrations are applied — see step 6.
WARNING
Removing or changing MODOBOA_PRO_LICENSE_KEY re-locks the plugin immediately: the stored hash no longer matches, so all features stay blocked until a fresh registration succeeds. An invalid_key response is terminal for that key — fix the value in settings.py and register again.
4. Accept virtual host hostnames
Each virtual host introduces a new public hostname. Django rejects requests whose Host header is not in ALLOWED_HOSTS, so the plugin provides a dynamic ALLOWED_HOSTS that automatically includes every virtual host's name.
In the same settings.py, replace your static ALLOWED_HOSTS with:
from modoboa_pro.virtualhosts.allowed_hosts import VirtualHostAllowedHosts
ALLOWED_HOSTS = VirtualHostAllowedHosts(extra=["localhost", "mail.example.com"])Pass any hostnames that are not virtual hosts (your primary hostname, localhost, monitoring probes…) via extra. Virtual host names are added automatically and the lookup is cached (and refreshed when virtual hosts change), so this stays cheap on every request.
WARNING
If you keep a plain ALLOWED_HOSTS = [...] list, requests to newly created virtual hosts will be rejected with a 400 Bad Request until you add each hostname by hand. Using VirtualHostAllowedHosts avoids that.
5. Apply database migrations
The plugin ships migrations for the VirtualHost model and for the licensing state. Apply them:
$ cd <modoboa_instance>
$ <venv>/bin/python manage.py migrate6. Register the installation
With the key configured (step 3) and migrations applied, register the install explicitly to activate the plugin and confirm the key is valid — instead of waiting for the lazy phone-home triggered by the activation screen:
$ <venv>/bin/python manage.py register_licenseOn success it prints Registered (instance <id>). and the plugin's features unlock. If it reports that no key is configured, an invalid key, or a network error, fix the cause and re-run — the command is safe to repeat.
7. Collect static assets
The plugin ships the built frontend (the federation remote that the Modoboa web interface loads at runtime) as static files. Collect them so your web server can serve remoteEntry.js:
$ <venv>/bin/python manage.py collectstatic --noinputCaching and upgrades
The federation remote is served under a fixed filename (remoteEntry.js), so browsers (and any CDN/reverse proxy in front of Modoboa) can keep serving a stale copy after an upgrade. The symptom is a frontend error such as Error: [Module Federation] Module ./SomeComponent does not exist in container — the host loads an old remoteEntry.js whose container predates the component the upgraded Python package now references.
The simplest durable fix is to tell your web server not to cache that one file, so it is revalidated on every load:
location = /static/modoboa_pro/remoteEntry.js {
add_header Cache-Control "no-cache"; # revalidate every load (allows 304)
}no-cache (not no-store) still permits a 304 Not Modified when nothing changed, so the cost is negligible. If you front Modoboa with a CDN, purge the remoteEntry.js URL there too after an upgrade.
8. Restart services
Restart the Modoboa web process and the RQ workers so the new code, settings and parameters are loaded:
$ systemctl restart modoboa # web / uwsgi
$ systemctl restart modoboa-rq # background workersTIP
Service names depend on how your instance was deployed (systemd units, supervisor programs, Docker services…). Restart whatever runs the Modoboa web process and the RQ workers for both the modoboa and privileged queues.
9. Verify
Log in to the Modoboa web interface as a super administrator. You should see a new Virtual hosts entry in the admin navigation (globe icon). Opening it shows the (initially empty) virtual host list.
If the menu entry is missing:
- confirm all three apps are in
MODOBOA_APPSand the web process was restarted; - confirm
collectstaticran and your web server serves the plugin's static files (the browser console will show a failed request forremoteEntry.jsotherwise); - confirm you are logged in as a SuperAdmin — the menu entry and the Virtual hosts route are restricted to that role.
If the menu entry appears but the features are locked behind an activation screen, the installation is not registered: re-check MODOBOA_PRO_LICENSE_KEY and re-run register_license (step 6).
Upgrading
To move an existing install to a newer release:
$ <venv>/bin/pip install -U modoboa-pro
$ cd <modoboa_instance>
$ <venv>/bin/python manage.py migrate # apply any new migrations
$ <venv>/bin/python manage.py collectstatic --noinput # refresh frontend assets
$ systemctl restart modoboa # web / uwsgi
$ systemctl restart modoboa-rq # background workersWARNING
pip install -U alone does not refresh the files your web server serves — they live in STATIC_ROOT and are only updated by collectstatic. Skipping it leaves the old remoteEntry.js in place, which breaks the frontend (see the caching note in step 7).
After upgrading, also remember the fixed-filename caching caveat from step 7: if you did not apply the no-cache rule, browsers and CDNs may still serve the old remoteEntry.js. A hard refresh (Ctrl+Shift+R) confirms the diagnosis; the Cache-Control rule (or a CDN purge) fixes it for end users who cannot clear their cache.
Next step
The plugin is installed but the certificate and Nginx features are disabled by default. Continue with Configuration to enable them.