KeyHelp: The Free Plesk Alternative I Moved To (and Its API Traps)
Plesk license expired, panel dead. I moved to KeyHelp: non-interactive install, one client per company, and the API gotchas that cost me hours.
The Plesk license on a box I run for a small business expired, and with it most of the panel. Sites kept serving, but anything that needed the panel to do something — new domains, mailboxes, certificates — was dead. I wrote about what still works in that state in Plesk with an expired license. This post is about what I moved to instead.
TL;DR: KeyHelp is a free hosting panel from Keyweb. It installs non-interactively on a clean Ubuntu 24.04 and brings the full classic stack: Apache + PHP-FPM in several versions, MariaDB, Postfix/Dovecot with Rspamd, BIND, ProFTPD, webmail, fail2ban, an iptables firewall and backups. It's good. But the REST API is off by default, half the client fields are read-only, and a new client gets a 1 MB quota that will make your first mailbox fail. Details below.
Install
Fresh Ubuntu 24.04 LTS, nothing else on it:
wget https://install.keyhelp.de/install_keyhelp.sh
bash install_keyhelp.sh --non-interactive
That's it. It runs for a while, pulls in everything, and at the end drops a file with the admin credentials into /root (named keyhelp_login_data_<date>_<time>). Read it, store the password in your password manager, delete the file.
Update (October 2026): a lot of wiki and forum posts still point at a get_keyhelp.php download URL. When I checked it in October 2026 it returned 404. Use the install.keyhelp.de/install_keyhelp.sh URL above.
Two more install notes:
- Keyweb's docs list Ubuntu up to 24.04 as supported. The installer itself does support 26.04 as well, but for a production box I'd stick with what's documented.
- Set the hostname, the A record and the reverse DNS before you install. The panel and Postfix pick up the FQDN, and fixing a mail server's identity afterwards is no fun.
If you forget the admin password or just don't want to type it, there's a nice escape hatch:
ssh root@203.0.113.10 "keyhelp login"
That prints a one-time login link. Click, you're in. I use it more than the actual password.
What you get
Out of the box, without touching a single package:
| Area | Component |
|---|---|
| Web | Apache with PHP-FPM, multiple PHP versions side by side |
| Database | MariaDB, phpMyAdmin / Adminer |
| Postfix, Dovecot, Rspamd, webmail (Roundcube / SnappyMail) | |
| DNS | BIND (optional, per domain) |
| FTP | ProFTPD |
| Security | fail2ban, iptables firewall with INPUT policy DROP |
| Ops | backup tooling, AWStats, disk quota |
The multi-PHP part was the main reason I picked it over a bare Ubuntu + certbot setup. I had one legacy site that needs PHP 7.4 and newer apps on 8.3/8.4, and that's a checkbox per domain here instead of an afternoon of PPAs — more on that in running multiple PHP versions on one server.
Structure: one client per company
The box serves several separate businesses. In KeyHelp terms each one becomes its own client, and a client is a real boundary:
- its own Linux user (files under its own home, PHP-FPM pools run as that user)
- its own domains, databases, mailboxes
- its own disk quota
So if one company's WordPress gets popped, it's sitting in a different Linux user than the neighbours. That's the main thing I wanted from a panel anyway — I can do the rest by hand, but per-tenant isolation done consistently is tedious without one.
Gotcha: don't create a Google Workspace domain as a mail domain
One of the companies runs its mail on Google Workspace. Its MX points at Google, and that has to stay that way.
If you add that domain in KeyHelp as an email domain, the local Postfix considers itself responsible for it. Every mail generated on this box to that domain — contact forms, WordPress notifications, cron output — gets delivered locally into mailboxes nobody reads instead of going out to Google. Classic split-brain mail.
So: create it as a web domain only, mail off. Postfix itself is cleanly configured from the start — check it if you're paranoid:
postconf mydestination
cat /etc/mailname
Both should only contain localhost and the server's own hostname, not your clients' domains.
Gotcha: the API is invisible until you turn it on
I wanted to script client and mailbox creation instead of clicking. KeyHelp has a REST API at /api/v2. Except:
- It's off by default. The menu entry lives under Configuration → API, and it's not shown at all until the API is enabled. I went looking for a menu item that simply didn't exist yet.
- The API key is stored hashed in the database. Copy it when it's shown. You can't read it back later, and inserting your own key into the DB by hand is blocked — don't try, it's the wrong approach anyway.
- An empty IP field on the key means no IP restriction. Fill it in if your scripts run from a fixed host.
Calling the API from the server itself is slightly awkward because the panel's hostname resolves to the public IP. --resolve pins it to localhost while keeping the correct SNI/Host:
curl -s --resolve panel.example.com:443:127.0.0.1 \
-H "X-API-Key: $KH_API_KEY" \
https://panel.example.com/api/v2/clients
Gotcha: PUT returns 200 and does nothing
This one is nasty. A client object has resource_limits and permissions. In the API they're read-only. If you PUT /clients/{id} with new values in those fields, you get a cheerful 200 OK — and nothing changes. No error, no warning.
The only way to change limits is through a hosting plan: create a plan with the limits you want, then assign it to the client.
# 1. create a plan (fields per the API docs) – returns the new plan id
curl -s --resolve panel.example.com:443:127.0.0.1 \
-H "X-API-Key: $KH_API_KEY" -H "Content-Type: application/json" \
-X POST -d @plan.json \
https://panel.example.com/api/v2/hosting-plans
# 2. attach the plan to the client
curl -s --resolve panel.example.com:443:127.0.0.1 \
-H "X-API-Key: $KH_API_KEY" -H "Content-Type: application/json" \
-X PUT -d '{"id_hosting_plan": 2}' \
https://panel.example.com/api/v2/clients/3
Always re-GET the object after a write. A 200 from this API is not proof of anything.
Gotcha: the 1 MB default quota
Which brings me to why I needed plans in the first place. A client created without a plan gets a disk quota of 1 MB. Then you try to create a mailbox with, say, 5 GB, and get:
max_size property is bigger than assigned disk space
Took me a minute to realise the problem wasn't the mailbox, it was the client. I now have a "Standard 50G" plan and every new client gets it at creation. Do that first and you'll never see this error.
Gotcha: keyhelp-toolbox hangs over SSH
keyhelp-toolbox sounds like the CLI you'd want for automation. It's an interactive menu. Run it as a one-liner over SSH (ssh root@host keyhelp-toolbox ...) and it just sits there waiting for input that never comes. Use it in a real interactive session, or use the API.
Similarly, the panel's internal cron job scripts aren't meant to be run standalone — the SSL maintenance job crashed when I tried calling it directly. There's a supported way to trigger it, which I cover in the Postfix SNI post.
Gotcha: "Add domain" eats your input
UI bug, but it bit me repeatedly. In Add domain, after you pick the owner and hit Continue, the form sometimes reloads a few seconds later. Anything you typed into the General tab in that window — subdomain, name, parent domain — is silently gone, and you save a domain with wrong or empty fields.
My routine now: Continue, wait 5–6 seconds, then fill in the fields, and check them once more before Save.
What I'm running behind it
Not everything is a PHP site. Some apps are Docker stacks, and those live on the same box bound to 127.0.0.1, with KeyHelp's Apache in front as reverse proxy via per-domain directives — see Apache as reverse proxy for Docker apps. Fair warning: Docker and the KeyHelp firewall don't get along out of the box. That's its own post: KeyHelp's firewall breaks Docker.
Verdict
For "several small businesses, websites + mail, one server, one admin", KeyHelp does what Plesk did for me, for free, without a license that can expire and take the panel with it. The UI is fine, the stack underneath is standard Debian/Ubuntu packages you can still debug by hand, and the per-client isolation is real.
The API is the weak spot. It works, but read-only fields that accept writes silently and a hidden menu entry are the kind of thing that eats an evening. Use plans from day one, verify every write with a GET, and keep keyhelp login handy.
Before you migrate anything to a new box, though: lower your DNS TTL first. I learned that one the slow way.