# Cipi: a shell-driven LEMP hosting panel for one VPS

> Cipi turns an Ubuntu VPS into a multi-app PHP hosting platform driven entirely by a CLI, with per-app isolation, zero-downtime deploys, Let's Encrypt, queue workers and S3 backups. It suits a developer who wants a Laravel app on a small VPS without a control panel, and it is Linux-only and opinionated about the stack.

**cipi-sh/cipi** — Easy Laravel Deployments

- Repository: https://github.com/cipi-sh/cipi
- Website: https://cipi.sh
- Stars: 1,147 · Forks: 213
- Language: Shell
- License: MIT
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/cipi-sh-cipi

## A hosting panel that is a CLI on your own server

Cipi describes itself as Easy Laravel Deployments, and the two claims in the tagline are the whole product. The first command installs a complete production stack on a VPS, the second deploys your application. What it deliberately is not is a web panel. The project's own framing is no panel, no bloat, and just SSH and the cipi command, and the optional web GUI is described as an extra, with one login for every Cipi server, where the CLI does everything without it. The target user is a developer with a single VPS and one or a handful of apps, not an agency running a hundred sites. The mechanics are all SSH and shell: the project is written in Shell, the repository has a setup.sh at the root, a lib/ directory for the command implementation, a cipi entry point, and a tests/ directory. There is also a CHANGELOG.md, a SECURITY.md and a version.md, which is the shape of a tool that expects to be updated and audited.

## Installing: one piped command, run on the server

The installation instruction is a single line, and the documentation is emphatic about where it runs:

```bash
wget -O - https://cipi.sh/setup.sh | bash
```

That command must be run on the server over SSH, not on your own machine, as a user with sudo. It targets a fresh Ubuntu 24.04 or 26.04 VPS, and the project states it works on DigitalOcean, AWS EC2, Hetzner, Vultr, Linode, OVH, Google Cloud, Scaleway and more, with a nominal install time of about ten minutes. The installer asks for the public half of your SSH key, which the documentation points out is ~/.ssh/id_ed25519.pub on your machine, and it notes that ssh-keygen -t ed25519 creates one if you have none, including on Windows from PowerShell or WSL. It then ends by printing a root password, which you are told to keep in a password manager. The security model that follows from that is stated clearly: root is reachable only by logging in as the cipi user with that key and then running su root. So the installer trades a password you must store for direct root login, and you should decide whether you are comfortable with that before running a piped script as root on a machine with a public address.

## Three steps from a blank box to a live domain

The workflow after installation is a menu-driven wizard followed by two commands. The wizard starts with cipi app create and offers Laravel as the default, then custom, then Node spa, static or ssr:

```bash
cipi app create
```

For a Laravel app you choose PHP-FPM or Octane, MariaDB or PostgreSQL, and then supply a username, a domain, a git repository, a branch, a PHP version and an optional asset build step described as npm ci && npm run build. The custom path asks for a user, an Nginx configuration and PHP-FPM, with git optional, and an empty repository meaning SFTP-only uploads to ~/htdocs. One detail in the wizard matters for automation: the deploy key is tested against the repository before you leave the wizard, and if it cannot read the repository, Cipi offers to save a forge token and adds the key. Then deployment is two commands:

```bash
cipi deploy myapp
cipi ssl install myapp
```

The first publishes a release, the second obtains a certificate. Both take the app name, so the app name is the key that everything else in the CLI is organised around.

## Every app gets its own user, pool and database

The isolation model is the part that makes a single box viable for more than one project. Each app runs under its own Linux user with an isolated filesystem, its own PHP-FPM pool or Octane process, and its own database. The stated consequence is that a compromise in one app cannot touch the others. On top of that sit the operational pieces a Laravel app actually needs. Nginx reverse proxies with per-app virtual hosts. PHP is selectable per app from 7.4 to 8.5 and hot-swappable. MariaDB is the default with PostgreSQL optional, and every Laravel app gets a dedicated database and user. Supervisor runs queue workers as per-app pools, either queue:work or Horizon, with optional Reverb for WebSockets over wss with credentials and file descriptor limits. Meilisearch is available as a native binary for Laravel Scout, bound to loopback only, with a per-app scoped key and index prefix. The runtime is PHP-FPM by default, with optional Laravel Octane on FrankenPHP per app through a --octane flag.

## Deploys are a symlink swap, and rollback is a flag away

The deployment model is the classic release-directory pattern, and the documentation is specific about it. Deployer handles Laravel apps with an atomic symlink, keeping 5 releases, and it supports rollback, plus an optional Node build step. Custom apps behave differently: Deployer clones straight into htdocs with no releases and no shared directory, and the Nginx config for that path is spelled out as index index.html index.php with try_files $uri $uri/ /index.php?$args and an error_page 404 /404.html. Health checks are HTTP probes every 5 minutes, and there is a post-deploy check with optional automatic rollback of a broken release. That last feature is the one to understand carefully: automatic rollback triggers on a failed probe, so a slow application that fails its first health check may be reverted even though the deploy was fine. Whether you enable it depends on how your app warms up. SSL comes from Let's Encrypt through Certbot over HTTP-01 by default, with optional DNS-01 through Cloudflare and wildcard support.

## The security defaults, and how they are tuned

The hardening is on from the first boot rather than being an add-on. UFW denies all inbound traffic except SSH, HTTP and HTTPS, and fail2ban bans the addresses that keep failing SSH logins. The documented defaults are three failed logins before a ban, counted over one hour, with a 24 hour ban that grows with each repeat up to 7 days, and three bans inside 24 hours meaning 7 days. Only the failure count is described as adjustable. CrowdSec can be added as a firewall bouncer, and Cloudflare Zero Trust is available through cipi zt for a tunnel, Access and an origin that stays dark, and there is a nightly integrity and upload scan. Configs are encrypted at rest with AES-256 in a Vault, log rotation is included and described as GDPR compliant, and per-app resource limits through cipi app limits cap FPM children, memory, Octane workers and queue processes. A repair command, cipi app fix-permissions, restores the expected layout if a deploy or a zip extracted as root left the home directory unreadable, which tells you the failure mode is a real one.

## The --custom path and the configuration file

The two deployment models are genuinely different products sharing one CLI, and a team should pick one deliberately. A Laravel app gets releases, a shared directory, a database, a .env, cron and workers. A custom site gets a clone into htdocs, a configurable docroot, and explicitly no database, no .env, no cron and no workers, with the Nginx block as quoted above. For a static site or a small PHP script that is exactly right, and it avoids a pile of machinery. Configuration for the richer mode is split between the CLI and an optional per-project cipi.yml. The command cipi ini handles php.ini, while cipi.yml covers aliases, www, basic auth, redirects and proxies, search, databases, workers, Reverb, limits, the deploy recipe, forced HTTPS, required .env names, scheduled commands, backups and post-deploy steps. That last group is the interesting one: required .env names means the tool can insist a variable is present before a deploy, which turns a missing secret into a failed deploy rather than a broken site. Backups are configured as profiles covering what, how often, where and how long, targeting S3, S3-compatible or local storage with client-side encryption.

## Where Cipi is the wrong tool

The constraints are clear enough to state. The tool is Linux-only and assumes a fresh Ubuntu 24.04 or 26.04 machine with root, so it does not fit an existing Debian box you cannot rebuild, a Windows server, or a managed platform with no shell access. It also does not talk to the provider's APIs, so a VPS that must be created, snapshotted or scaled by script is not covered. The default profile assumes MariaDB or PostgreSQL with a database per app, which is fine for a handful of apps and expensive for many, since every app is isolated at the user, pool and database level. The single-tenant assumption shows up in a second way: one SSH key and one cipi user, with the root password printed once at install, is a workable model for one operator and an awkward one for a team with several administrators. The project also leans on a set of external services being reachable, namely Let's Encrypt, optional Cloudflare, optional Meilisearch and S3 for backups, so a deployment in a restricted network has to be planned around that. And the plugin and panel alternative is not the same product: the WHMCS topic in the repository reflects that Cipi can sit alongside that ecosystem rather than replace it.

## How this differs from a managed Laravel host

The alternative is a managed Laravel platform, where the vendor runs the server, the PHP versions, the database and the certificate, and you push a git branch. The difference in approach is where the operational work lands. The managed host takes the security surface away from you entirely, and it also takes away the ability to change the stack, to add Octane where a shared host would not allow it, to pick PHP 7.4 for one app and 8.5 for another, or to add a Supervisor pool with Horizon. Cipi gives you that freedom because it is root on your own box, and the cost of that freedom is the entire sysadmin surface: you own the patching, the firewall, the backups, the certificate renewal and the monitoring, and you own the moment something goes wrong at three in the morning. There is a third option worth naming, which is the traditional control panel such as Plesk or a WHMCS-based stack, where you get a graphical interface and a larger surface area with more configuration than most Laravel developers want. The honest comparison is not features, it is who carries the pager.

## Conclusion

Adopt Cipi if you want to run one or a few Laravel or Node applications on your own Ubuntu VPS, dislike web panels, and are willing to learn a CLI whose vocabulary is your app names and config files. Do not adopt it if you need Windows support, a shared hosting environment, or a provider-specific deployment API, since the tool assumes root on Ubuntu and talks to hosts only over SSH. Before you run the installer, note that it asks for the public half of your SSH key, ends by printing a root password to keep in a password manager, and makes root reachable only through the cipi user, and check the requirement of a fresh Ubuntu 24.04 or 26.04 machine.

## FAQ

### How do I install Cipi on a VPS?

Run `wget -O - https://cipi.sh/setup.sh | bash` on the server over SSH, as a user with sudo, on a fresh Ubuntu 24.04 or 26.04 machine. The installer asks for the public half of your SSH key and prints a root password at the end.

### What does Cipi do once it is installed?

You create an app with `cipi app create`, choosing Laravel by default, a custom site, or a Node spa, static or ssr app, then deploy with `cipi deploy myapp` and obtain a certificate with `cipi ssl install myapp`.

### How does Cipi isolate one app from another on the same server?

Each app runs under its own Linux user with an isolated filesystem, its own PHP-FPM pool or Octane process, and its own database, so the project states that a compromise in one app cannot touch the others.

### What is the difference between a Laravel app and a --custom app in Cipi?

A Laravel app gets a zero-downtime deploy with an atomic symlink and 5 releases, a database, workers and cron. A custom app is a classic clone into htdocs with a configurable docroot and no database, no .env, no cron and no workers.

### What licence is Cipi released under?

The project is licensed under the MIT Licence, with the LICENSE file at the root of the repository. The current release is version 5.6.0 and the last push to the repository was on 2026-10-19.

## Sources

- [cipi-sh/cipi on GitHub](https://github.com/cipi-sh/cipi)
- [License: MIT](https://github.com/cipi-sh/cipi/blob/master/LICENSE)
- [Project website](https://cipi.sh)
- [README](https://github.com/cipi-sh/cipi/blob/master/README.md)
- [Releases](https://github.com/cipi-sh/cipi/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/cipi-sh-cipi
