Case studySaaS · Cloud marketplace · Server automation
WordPress Hosting Control Panel
A self-serve SaaS for running WordPress on your own cloud. A customer connects a cloud account; the panel creates the server, installs a tuned LEMP stack and a management agent, and from then on runs the site, SSL, PHP, database, cache, backups, files, SSH and WordPress itself from the browser — with Stripe billing on top.
I built it alone over two years and three generations — from a central API for Google Cloud servers in 2023 to a platform that provisions on five clouds — plus a single-server edition sold as a licensed Google Cloud Marketplace image.
Role
Sole engineer — design, engineering & coding
Editions
Multi-cloud SaaS · Marketplace image
Timeline
Apr 2023 – Feb 2025 · 3 generations
Status
v1 shipped February 2025
The problem
Customers want WordPress on their own cloud account — their server, their bill — without learning to run a Linux server.
What was built
Morsalin, as sole engineer, built a panel that provisions servers on five clouds and runs them through a signed Python agent, plus a Google Cloud Marketplace edition.
The result
v1 shipped February 2025. Sites, SSL, PHP, databases, backups and WordPress itself are run from the browser, with Stripe billing on top.
~29.8klines of PHP
~42.2klines of Vue
~1.2klines of Python agent
~3klines of JS, CSS & server templates
5cloud providers
~19klines of PHP in generation 1
74pages
~270routes
3generations
2editions
~22klines in the Marketplace edition
5 minsigned installer links
01The problem
Running WordPress well means running a server well.
01
A managed host owns your infrastructure.
Convenient, but the server, the network and the bill belong to someone else.
02
A bare VM owns your weekend.
nginx, PHP pools, databases, certificates, backups and updates all have to be learned, configured and kept running.
03
The middle path.
The customer keeps the cloud account and the bill. The panel does the operations — provisioning, tuning, certificates, backups, updates and troubleshooting — all from a browser.
02What it does
Everything a WordPress server needs, in a browser.
Provisioning
Creates an Ubuntu server on five clouds, with regions, sizes and plans fetched live from each provider.
Web & PHP
nginx with HTTP/2 and HTTP/3, security and tuning; PHP 8.1 to 8.4 with FPM pool tuning, logs and metrics.
Data
MariaDB databases, users and grants with phpMyAdmin single sign-on; Redis configuration.
SSL
Let’s Encrypt over HTTP or DNS through five DNS providers, uploaded certificates, or the panel’s own authority.
Backups
Encrypted, deduplicated snapshots to eight storage providers, on schedules from hourly to monthly, with restore.
WordPress
Install, updates, themes, plugins, cron, maintenance mode, magic login, database tools and migration from other hosts.
Files & services
A file manager with an in-browser code editor, SSH keys, systemd services, processes and live stats.
Self-healing
About thirty health checks, each with a one-click fix — including configuration drift.
Billing
Stripe subscriptions with plan-based site limits, invoices, tax IDs and the billing portal.
03Architecture
One site, one server, one agent.
fig.02 — system architecturepanel · cloud · agentscroll →
One site, one server
The first design modelled servers, domains and sites separately, like a traditional panel. Halfway through, I collapsed them: a site is a server. It removed a layer of indirection from every screen and command, made isolation physical rather than configured, and let one model own the whole stack.
Configuration as templates
nginx, PHP pools, MariaDB, Redis, logrotate, phpMyAdmin, backup remotes and the install scripts are all rendered per site. The same templates make drift detection possible — and every fix is just a re-render.
04The server agent
An agent, not SSH.
The panel never opens a shell on a customer’s server. Each server runs a small agent I wrote in Python — Flask on gunicorn, run by systemd — and the panel talks to it over HTTPS once DNS and the certificate verify. In production, the agent’s port answers only the panel’s address, enforced by the cloud firewall on all five providers.
01
Tokens bound to one machine
Every request carries a token that expires within the hour. The agent verifies its signature against the signing key’s published certificates, then checks that its audience is a hash of the server’s own public address — so a token issued for one machine is refused by every other.
02
Privilege drops per request
File-manager and SSH-key operations switch to the site’s own Linux user before touching the disk, so the agent’s root access is never used on a customer’s files.
03
A deliberately small surface
Eight modules — processes, Linux users, SSH keys, file manager, file system, MariaDB, stats and systemd — and nothing else. Run by systemd, restarted automatically.
04
Shipped as native code
The Python modules are compiled with Cython into native extensions and installed as an operating-system package from a signed repository.
05Self-installing servers
A server that installs itself.
1
The cloud creates the VM
With first-boot data in the format each provider expects.
2
First boot fetches the installer
It checks it is root, exits early if a previous install finished, and fetches the second stage from a signed URL that expires in five minutes.
3
The stack installs
nginx, MariaDB, Redis, PHP 8.1–8.4, backup and WordPress tooling and the agent, from a signed package repository — with swap on small machines and only the needed ports opened.
4
A retry that knows when to stop
A systemd timer re-runs the installer until the agent is present, and does nothing after that.
5
The panel connects
A scheduled job polls until the agent answers, then turns the deployment into a live site — or gives up cleanly after two hours.
Every step checks before it acts, so running any part twice is harmless.
06Commands that succeed
A command only reaches a server when it can succeed.
Validation first
182 request classes validate every input — domains, directory names, plugin slugs, database identifiers — before anything reaches a server.
Preconditions as middleware
28 middleware say when an action is allowed: site online, WordPress installed, certificate type, plan limit. Each has a page guard and an API guard, so the UI never offers what the API would refuse.
Reconciliation on change
Model observers turn edits into server changes. A new hostname reissues the certificate and rewrites nginx; a new alias re-renders exactly the files it affects and restarts only what changed.
Drift detection
Every configuration file is a template, so the panel can compare what is on a server with what it would write — and fix the difference.
07Security
Least privilege, all the way down.
Encrypted credentials
Cloud and DNS keys are encrypted at rest and never serialised; a credential cannot be deleted while a site depends on it.
Least privilege
Commands, files, wp-cli and backups run as the site’s own Linux user; the default PHP pool disables process and socket functions.
Arguments, not shell strings
Commands built from user input — installing a plugin, generating a login link, checking DNS — are passed as argument lists.
SQL by the query builder
Server-side database administration is built and escaped by the framework’s query builder, with system databases protected.
Signed, expiring links
For the installer, email verification, administrator access and phpMyAdmin single sign-on (encrypt-then-MAC, five-minute expiry).
A private certificate authority
Issues 4096-bit certificates covering every domain and alias, with serial numbers generated to the standard’s rules.
08Backups & WordPress
The work a site owner never wants to do.
Backups
Encrypted, deduplicated restic snapshots shipped through rclone to AWS, Cloudflare, DigitalOcean, Linode, Wasabi, Vultr, Alibaba or Google Cloud Storage. Twelve cadences fan out creation, archiving and pruning, and unique job keys stop two snapshots of one site from overlapping.
WordPress
Everything through wp-cli as the site user: install, core, themes and plugins with auto-update, options, cache, cron run by the panel on schedule, maintenance mode, salts, magic login links and a full set of database tools.
Live interface
Every change broadcasts over private, ownership-checked channels, so pages update as servers come online. nginx and PHP-FPM metrics are collected hourly, and files open in an in-browser Monaco editor.
09Marketplace edition
The same idea, sold as a licensed server image.
A single-server edition for Google Cloud Marketplace: two more applications — a small central licensing and installer service, and a panel that runs on the customer’s own VM. Launch the image, and it installs, licenses and configures itself with no one in the loop.
fig.03 — from marketplace to running serverlicence · install · panelscroll →
Only licensed machines install
First boot receives a signed installer link valid for five minutes. The VM posts its licence codes from the cloud’s metadata server, and every one must belong to a published image — images, regions and zones synced from the Compute Engine API, not typed in.
An installer that is safe to re-run
It adds the signed package repository, installs and verifies each package, prepares nginx, enables services, and adds swap on small machines only if none exists.
Setup checked at every step
Root and a valid licence required; a password checked against known breaches; the hostname’s DNS must point at the VM before a certificate is requested; the panel is then served by Caddy over TLS.
Every site in its own sandbox
Each site runs under its own Linux user and PHP-FPM pool with open_basedir and disabled process functions; wp-cli and composer run as that user, and passwords reach wp-cli and MySQL over standard input.
January – February 2024
95 commands
It started as a command-line tool — sites, databases, users, certificates and WordPress, each a short shell command — then the logic moved into reusable libraries and the machine learned to read its own cloud metadata.
March – July 2024
A web panel
A JSON API and a Vue interface replaced the commands over the same libraries: 23 pages, 111 routes and 50 validated request types, with one setup command left for the admin account, hostname and certificate.
10How it evolved
Two years, three generations.
Apr 2023Generation 1
A central Laravel API managing WordPress servers launched from Google Cloud images, talking to an agent on each server over HTTP.
Jun–Sep 2023
A server onboarding pipeline — instance metadata, hostname DNS, hostname SSL, web server preparation and agent handshake — plus an admin for images, regions and zones synced from the Compute Engine API; the interface moved to its own TypeScript app.
Jan–Jul 2024Marketplace edition
A second edition branched off: a single-server panel sold as a licensed Google Cloud Marketplace image — first as a 95-command CLI, then as a web panel.
Servers, domains and sites collapsed into one site-per-server model, and the agent rebuilt around it — the largest rewrite of the project.
Nov 2024
Self-installing servers, certificate provisioners, restic backups and scheduled cadences.
Dec 2024
From “bring your own server” to “create one in your cloud”, on five providers; phpMyAdmin single sign-on.
Jan–Feb 2025
Cloud-init installer, site migration, support — and the v1 release.
Each generation kept what worked — the agent, the templates, the WordPress toolkit — and made the model simpler: a fresh codebase in 2024, teams dropped early, then servers, domains and sites collapsed into one. Every simplification made later features cheaper to build.
11Key decisions
Every non-obvious choice, with its reason.
One site per server
Isolation becomes physical, and one model owns the whole stack.
An agent over HTTPS, not SSH
No shell on customer servers; access is scoped, signed and short-lived.
The agent checks its own address
A token only works on the machine it was issued for, verified on that machine.
Signed, five-minute installer URL
A copied install command stops working almost immediately.
Check before every install step
Re-running provisioning is always safe.
Validation and precondition middleware
A command only reaches a server when it can succeed.
Templates for every config file
The same source renders, audits and repairs a server.
Run as the site’s Linux user
A compromised site cannot reach the rest of the machine.
Enums generated into the frontend
Backend and UI can never disagree on a value.
Drop teams early
A simpler model made every later feature cheaper to build.
Licence by image licence codes
It is how the marketplace marks paid VM images, and it needs no customer credentials.
Passwords over standard input
Nothing sensitive in process lists or shell history.
12Outcome
Hosting without a terminal.
Provisioning
A server created in the customer’s own cloud on five providers, installing and connecting itself.
Operations
nginx, PHP, MariaDB, Redis, services, files and SSH managed entirely from the browser.
Resilience
Scheduled encrypted backups to eight providers, and self-healing checks with one-click fixes.
Business
Stripe subscriptions with plan-based limits, and a licensed Marketplace edition for single servers.
✓Hosting without a terminal — every routine operation a WordPress site needs is a button, a form or a schedule.
✓Customers keep their infrastructure — the server and the bill stay in their own cloud account.
✓Pages update live as servers come online and jobs finish, over private, ownership-checked channels.
✓Built end to end by one engineer, from the install script on a fresh VM to the last page of the billing portal.
My role
Everything, alone, across both editions: the product itself, the data model, the provisioning flow for five clouds, the install scripts and server configuration templates, the on-server agent and its protocol, the Marketplace licensing service and single-server panel, the security model, billing, and every page. I designed, built and coded it, front to back.