DPanel is a free, open-source alternative to cPanel / Plesk / DirectAdmin for managing a single VPS. It runs as a Node.js service, serves a fast single-page admin UI, and orchestrates the boring-but-tedious bits of running web hosting: Apache vhosts, BIND9 DNS zones, Let's Encrypt SSL issuance, Postfix/Dovecot mail with deliverability monitoring, MariaDB management with an in-panel browser, scheduled backups + restore, file management, a Node/Python app runner via PM2, a webmail UI, traffic analytics, and a WebSocket-based browser terminal.
It's built from scratch — no Plesk wrappers, no aging Perl, no $45/month license.
Why? Because installing cPanel costs more than your VPS, and the UI was designed in 2003.
On a fresh Ubuntu 22.04 or 24.04 VPS as root:
curl -fsSL https://raw.githubusercontent.com/danhorntx/dpanel/main/install.sh | sudo bashThat's it. The installer:
- Installs the full stack (Apache, MariaDB, Postfix, Dovecot, OpenDKIM, BIND9, certbot, Node.js, PM2, UFW)
- Configures all services with sane defaults
- Generates secrets + a random admin password
- Brings the panel up at
https://<your-ip>:8080in 5–8 minutes
When it's done you'll get a summary with the login URL + auto-generated password. Save the password to your password manager — you can change it from Settings after first login.
# To pre-set the admin credentials (skip the random password generation):
sudo DPANEL_ADMIN_USERNAME=admin DPANEL_ADMIN_PASSWORD='your-strong-password' bash install.shAfter install: log in → Settings → Two-Factor Authentication → Enable. Highly recommended before doing anything else.
- Atomic domain provisioning — one click creates Apache vhost, SFTP user, BIND zone, mail records, autoconfig vhost, webmail proxy, and Let's Encrypt cert. 13-step reconciler with per-step rollback if anything fails.
- Domain Health dashboard — per-domain aggregator: SSL expiry, DNS, mail probe summary, backup age, disk usage, PHP version, recent Apache errors.
- Per-domain redirects —
.htaccessmanager for 301/302 URL redirects.
- Per-domain mail — MX, SPF, DKIM, DMARC, MTA-STS, TLS-RPT all auto-published.
- Mail Health probe — 11 deliverability checks including rDNS forward-confirm, HELO match, cert validity, and RBL listings on Spamhaus / Barracuda / SpamCop / SORBS. Runs daily, alerts on failure.
- DMARC aggregate report processor — IMAP fetches your daily reports, parses XML (gz/zip), stores per-source trends.
- Webmail — built-in IMAP/SMTP web client. Supports drafts, threading, attachments, spam scoring.
- Per-domain TLS via Dovecot SNI — each domain gets its own cert served correctly.
- Forwarding with local-keep — shadow address pattern keeps a local copy when forwarding.
- Two-Factor Authentication (TOTP) — works with any standard authenticator app.
- API Keys — bearer tokens with
adminorreadscope for external automation. Audit-logged, SHA-256 hashed. - Audit log — every state-changing admin action recorded with user, IP, target.
- Brute-force lockout — IP-tracked, 5 fails / 15 min → lockout.
- Domain scoping — non-admin users can be restricted to specific domains.
- In-panel database browser — phpMyAdmin-equivalent: tables, structure, row pagination, free-form SQL runner, audit-logged.
- Backups — files (tar.gz) + databases (gzipped mysqldump). Restore from the UI with
type-
RESTORE-to-confirm guard. - File manager — browse, edit, upload, chmod, archive extract (.zip / .tar.gz / .tar.bz2 / .tar), bulk delete + move, image preview.
- WordPress installer via WP-CLI — runs async via the job queue.
- Node.js / Python app manager — deploy long-running services with PM2 + Apache reverse proxy, auto port allocation, live log streaming.
- Git deploy — webhook or manual trigger per domain.
- DAnalytics — Apache log ingestion with geo + bot detection, dashboard, scheduled email reports, bot signals.
- Cron job manager — schedule, edit, list, remove.
- UFW firewall UI.
- PHP version switching per domain.
- Service health monitor with email alerts (SSL expiry, mail-health failures, backups, new-IP logins).
- WebSocket terminal in the browser.
- Fully responsive — usable on a phone, hamburger nav, table priority columns, bottom-sheet modals, touch-target sizing.
- Single-page admin app, no page reloads.
- Built-in changelog viewer + interactive user guide.
| Minimum | Recommended | |
|---|---|---|
| OS | Ubuntu 22.04 LTS | Ubuntu 24.04 LTS |
| RAM | 1 GB | 2 GB+ |
| Disk | 20 GB | 40 GB+ |
| CPU | 1 vCPU | 2+ vCPU |
| Network | Public IPv4 | + reverse DNS configurable |
For mail deliverability to work properly you'll also need:
- rDNS (PTR) configurable at your VPS provider (Contabo, Hetzner, DigitalOcean, Vultr — all support this)
- IP not on Spamhaus PBL (some VPS provider IP ranges are blanket-listed; check before committing)
DPanel runs comfortably on a $5/mo Contabo or DigitalOcean droplet for a single small site.
If you'd rather see what the installer does before running it:
Read install.sh and run step by step
# 1. Clone
git clone https://github.com/danhorntx/dpanel /opt/dpanel
cd /opt/dpanel
# 2. Read what we're about to run
less install.sh
# 3. Run
sudo bash install.shThe script is self-documenting — each step is logged as it runs. It's idempotent so safe to re-run if anything fails partway through.
What the installer does, in order
- Preflight — root check, OS check (Ubuntu 22.04 / 24.04 expected)
- apt install — Apache 2.4, MariaDB, Postfix, Dovecot, OpenDKIM, postsrsd, BIND9, certbot, vsftpd, UFW, build tools, dnsutils, jq, etc.
- Node.js 22 + PM2 — from NodeSource
- Apache modules — rewrite, headers, ssl, proxy, proxy_http, proxy_wstunnel
- MariaDB — creates
dpaneldatabase + user with a random password - UFW firewall — opens 22, 25, 53, 80, 443, 465, 587, 993, 995, and the panel port (8080)
- Clone DPanel to
/opt/dpanel, runnpm install --production .env+ secrets — generatesSESSION_SECRET, writes DB password, generates self-signed panel cert- Mail stack config — creates vmail user (uid 5000), configures Postfix
main.cffor virtual delivery via Dovecot LMTP, sets up Dovecot virtual user auth via/etc/dovecot/userspasswd-file, wires OpenDKIM milter into Postfix, configures postsrsd for SRS on forwards - systemd — installs
dpanel.service, starts it, runs schema migrations - Admin bootstrap — inserts the first admin user into the DB with a bcrypt'd password
- Print summary — login URL, credentials, useful commands
After install, in order:
- Log in at
https://<your-ip>:8080with the credentials from the install summary. - Settings → Admin Contact Email — set your real email. This is used for Let's Encrypt registrations and panel alerts.
- Settings → Two-Factor Authentication → Enable 2FA. Save the secret in a password manager before clicking Verify — there are no backup codes; recovery is via SSH.
- Set the server's reverse DNS — at your VPS provider's control panel, set the PTR for
your server IP to something like
mail.yourdomain.com(or<short-name>.yourdomain.com). This is one of the strongest deliverability signals. - Get a real cert for the panel itself (optional but nicer):
The panel keeps using its self-signed cert for the Node server; Apache reverse-proxies
# Point panel.yourdomain.com DNS at your server, then: certbot --apache -d panel.yourdomain.com --non-interactive --agree-tos -m you@example.companel.yourdomain.com→127.0.0.1:8080with the real cert. - Add your first domain — Domains → Add Domain. Check the "Set up mail" box if you want the full mail stack (MX, SPF, DKIM, DMARC, MTA-STS, autoconfig, webmail) created in one shot.
- Run the Mail Health probe for that domain — Mail → Health → pick domain → Run Probe. Fix any reds. (rDNS at the provider, RBL delisting if applicable.)
cd /opt/dpanel
git pull
npm install --production
systemctl restart dpanelSchema migrations run automatically on startup. They're all IF NOT EXISTS and safe to re-run.
For major upgrades, check the CHANGELOG.md — anything requiring operator action (new env vars, manual data migration) is called out in the release notes.
DPanel reads its config from /opt/dpanel/.env. Most values are set by the installer; you can
edit them later and systemctl restart dpanel.
| Variable | Purpose |
|---|---|
DB_HOST / DB_PORT / DB_USER / DB_PASSWORD |
MariaDB connection (defaults: 127.0.0.1, 3306, dpanel, generated) |
SESSION_SECRET |
64-byte hex string used to sign session cookies. Generated; never commit. |
DPANEL_SERVER_IP |
Server's public IP. Used in DNS records the panel writes. Defaults to hostname -I. |
DPANEL_NS1 / DPANEL_NS2 |
Vanity nameserver pair stamped into every zone's SOA MNAME and NS RRset. Defaults to ns1/ns2.danhorntx.com. Must match what the registrar delegates for the zones this host serves — see the note below. |
IMAP_LOCAL_SERVERNAME |
Fallback hostname used for IMAP TLS when no per-domain cert exists. Defaults to hostname -f. |
DPANEL_MAIL_HOSTNAME |
Postfix HELO / myhostname. Should resolve to your server's IP and match its PTR. |
PORT |
Panel listening port (default 8080). |
Optional (only set if you use the corresponding feature):
| Variable | Purpose |
|---|---|
DMARC_INBOX_EMAIL / DMARC_INBOX_PASSWORD |
Mailbox the DMARC report processor reads from. Daily 4:30am cron. |
REPORTS_FROM |
From address for scheduled analytics report emails. Defaults to reports@danhorntx.com. |
PANEL_BASE_URL |
Panel URL used in analytics report email links. Defaults to https://panel.danhorntx.com. |
DPANEL_SEED_GMAIL + _PASSWORD |
Seed account for deliverability testing (use a Gmail app password). |
DPANEL_SEED_OUTLOOK + _PASSWORD |
Same for Outlook (app password required). |
DPANEL_SEED_YAHOO + _PASSWORD |
Same for Yahoo. |
Every zone write re-stamps the apex NS RRset and the SOA MNAME from these values — not just zone creation, but any record edit. That is deliberate: a host must advertise its own nameservers. The consequence is that changing them and then editing any record re-delegates that zone in the child NS RRset. If the registrar still delegates elsewhere, resolvers that prefer the child RRset will query a host that does not serve the zone and fail intermittently rather than cleanly.
When a write actually changes the pair, DPanel logs it:
[dns] apex NS for example.com re-stamped: ns1.old.com + ns2.old.com -> ns1.new.com + ns2.new.com
Grep journalctl -u dpanel or logs/panel.log for dns:ns-restamp to audit it after the fact.
┌─────────────────────────────────────────────────────────────────┐
│ Browser (admin or webmail user) │
└───────────────────────────┬─────────────────────────────────────┘
│ HTTPS
┌───────────────────────────▼─────────────────────────────────────┐
│ Apache 2.4 — reverse proxy + Let's Encrypt termination │
│ • panel.yourdomain.com → 127.0.0.1:8080 │
│ • webmail.<domain> → 127.0.0.1:8080 (proxied) │
│ • <domain> → /var/www/<domain>/public_html │
│ • autoconfig.<domain> → static XML for mail clients │
│ • mta-sts.<domain> → static policy file │
└───────────────────────────┬─────────────────────────────────────┘
│
┌───────────────────────────▼─────────────────────────────────────┐
│ DPanel (Node.js / Express, port 8080) │
│ • server.js — Express app + cron scheduler │
│ • lib/ — primitives (apache, dns, mail, ssl, │
│ mysql-browser, totp, jobqueue, …) │
│ • lib/state/domain.js — atomic domain reconciler │
│ • routes/ — HTTP handlers + WebSocket terminal │
│ • public/ — single-page admin UI + webmail │
└───┬───┬───┬───┬───┬───┬─────────────────────────────────────────┘
│ │ │ │ │ │
▼ ▼ ▼ ▼ ▼ ▼
┌───┐┌───┐┌───┐┌───┐┌───┐┌───┐
│MDB││ A ││BND││PSX││DVC││OPN│ System services
└───┘└───┘└───┘└───┘└───┘└───┘
MariaDB Apache BIND9 Postfix Dovecot OpenDKIM
(Plus: postsrsd · certbot · PM2 · vsftpd · UFW · systemd)
DPanel runs as root because it shells out to system tools (useradd, systemctl, certbot,
apache2ctl, postmap, opendkim-genkey, etc.) and writes config files in /etc/. This is
deliberate and matches the pattern of every comparable control panel.
DPanel is plain Node.js — no build step, no transpilation.
# Local clone
git clone https://github.com/danhorntx/dpanel
cd dpanel
npm install
# Run against a remote MariaDB (set DB_* env vars in .env)
node server.jsCode style:
'use strict'at the top of every file- 2-space indent
- Files under 400 lines preferred
- Vanilla DOM in the frontend (no React/Vue) — keeps the SPA fast and zero-build
- Pool-based DB access via
mysql2/promise
Schema migrations: add ALTER TABLE ... IF NOT EXISTS or CREATE TABLE ... IF NOT EXISTS to
lib/db.js's migrate() function. Runs every startup.
Issues + PRs welcome at https://github.com/danhorntx/dpanel.
The repo also ships these helpful docs:
- CHANGELOG.md — version history
- DECISIONS.md — architecture decision log
For any feature that touches the system layer (Apache, mail, DNS) we generally expect:
- An idempotent change (safe to apply twice)
- A rollback path
- A documented migration path if existing production data needs to change shape
| Symptom | First check |
|---|---|
| Panel won't start | journalctl -u dpanel -n 50 --no-pager |
| 502/503 from panel.yourdomain.com | Apache → 8080 path: curl -k https://localhost:8080/ from the server; check /var/log/apache2/panel_error.log |
| Mail lands in spam | Mail → Health → Run Probe; fix rDNS, HELO, RBL, MTA-STS in that order |
| DB backup is 20 bytes | Pre-v2.0 bug — upgrade and take fresh backups |
| Webmail returns 403 | Check apache2ctl -S | grep webmail.<domain> — should resolve to its own vhost, not autoconfig's |
| Schema seems off | Restart dpanel; migrations auto-run |
| Lost 2FA / phone | SSH to server: mysql -e "UPDATE dpanel_users SET totp_enabled=0, totp_secret=NULL WHERE username='admin'" dpanel |
MIT — use it, fork it, sell it, host it. Attribution appreciated but not required.
Built with care over many late nights · contributions welcome · file an issue · releases




