Getting Started
Installing Cipi
Cipi installs a complete Laravel-ready production stack on Ubuntu 24.04 or 26.04 with a single
command. The installer takes roughly 10 minutes and sets up Nginx, MariaDB, PHP, Valkey
(optional PostgreSQL via cipi db install pgsql after install),
Supervisor, Fail2ban, UFW, Certbot, Deployer, and the cipi CLI itself.
From zero to production in three steps
# 1 — on a fresh Ubuntu 24.04 / 26.04 VPS, over SSH, as a user with sudo wget -O - https://cipi.sh/setup.sh | bash # 2 — create the app (Laravel by default) cipi app create # 3 — deploy it and put it behind HTTPS cipi deploy myapp cipi ssl install myapp
That is the whole path. First create an SSH key on your computer (below), then run the installer on the server — it will ask you to paste that public key. Everything after that is explained in the rest of this page.
sudo. Cipi provisions the whole box — it
is not something you point at a remote host from your laptop.Create an SSH key on your computer
Do this on your laptop or workstation, not on the VPS. The installer needs the
public half of the key so it can lock down the server: after setup you SSH in as
the cipi user with this key. Root SSH login is disabled. Never paste the private
key.
See if you already have one:
ls ~/.ssh/*.pub
If id_ed25519.pub or id_rsa.pub is listed, you already have a key — skip
generation and copy the public file with the command below. If the folder is empty or the command
errors, create a new Ed25519 key:
ssh-keygen -t ed25519 -C "you@example.com"
Press Enter to accept the default path (~/.ssh/id_ed25519). Set a passphrase if you
want (recommended). On macOS and Linux run this in Terminal; on Windows use PowerShell or WSL.
Show the public key — this is the single line you will paste when the installer asks:
cat ~/.ssh/id_ed25519.pub
It looks like ssh-ed25519 AAAA… comment. Copy the whole line:
- macOS:
pbcopy < ~/.ssh/id_ed25519.pub - Linux:
xclip -sel clip < ~/.ssh/id_ed25519.pub(or copy from the terminal) - Windows (PowerShell):
Get-Content $env:USERPROFILE\.ssh\id_ed25519.pub | Set-Clipboard
.pub file is public. The file named id_ed25519 (no
extension) is the private key — keep it on your computer and never paste it
into the installer or send it to the server.Run the installer on the server
SSH into the fresh Ubuntu VPS as a user with sudo, then:
wget -O - https://cipi.sh/setup.sh | bash
The wizard asks for the public key before any package is installed. Paste the line
you copied above (accepted formats: ssh-ed25519, ssh-rsa,
ecdsa). Cipi sanitizes comments, carriage returns, and extra whitespace, then creates
a dedicated cipi Linux user for admin SSH access and hardens SSH:
root login disabled, cipi public-key only (group cipi-ssh), app users
(group cipi-apps) can connect with password. Login attempts are limited to 3 with a
20-second grace period; X11 forwarding is disabled.
The installer also generates a random 40-character root password, stores it in
/etc/cipi/server.json, and displays it in the installation summary. It is not
an SSH password: root SSH login is disabled. It is what you type when, already logged in as the
cipi user with your key, you run su root. Keep it in a password
manager.
Non-interactive installation
For automated setups, pass your SSH public key via the SSH_PUBKEY environment variable
to skip the interactive prompt:
SSH_PUBKEY="ssh-ed25519 AAAA..." wget -O - https://cipi.sh/setup.sh | bash
AWS (root login disabled by default)
$ ssh ubuntu@your-server-ip $ sudo -s $ wget -O - https://cipi.sh/setup.sh | bash
At the end of the installation you will see a summary screen with the SSH access details, the
auto-generated root password, and the MariaDB root password.
Save them immediately — they are shown only once. Credentials are stored in
/etc/cipi/server.json, which — like all Cipi configuration files — is
encrypted at rest using AES-256-CBC via the built-in Vault
system. Credentials, SSH keys, .env files, and database dumps are protected both on
disk
and during transfer (via Sync encrypted archives). Cipi also enforces
GDPR-compliant log retention with automatic rotation policies for application,
security, and HTTP logs — see Log retention for details.
/etc/cipi/server.json.Post-installation access
After installation, admin access uses public-key authentication as the
cipi user (root login is disabled). App users can SSH directly with
ssh myapp@server-ip and the password generated at app creation. To run
cipi commands or perform any administrative task, connect as cipi and
then escalate:
# 1. connect to the server (key-based auth only) $ ssh cipi@your-server-ip # 2. escalate to root to run cipi commands cipi@server:~$ sudo -s # 3. now you can use all cipi commands root@server:~# cipi status root@server:~# cipi app list # 4. to work as an app user, switch with su root@server:~# su - myapp
Requirements
- Ubuntu 24.04 or 26.04 LTS (fresh installs only; interim releases are not supported)
- Root access (or
sudo -son AWS) - Ports 22, 80, and 443 open
- A clean server — do not install Cipi on a server with an existing web stack
- A public internet-facing server with a routable public IPv4 address — NAT-only VPS, home networks behind a router, and hosts not reachable from the internet are not supported
- Full KVM or bare-metal virtualization — not OpenVZ, LXC containers, or shared-kernel environments (including local Docker, Docker Desktop, OrbStack, Colima, WSL, and similar desktop container runtimes)
Cipi is tested and works on: DigitalOcean, AWS EC2, Vultr, Linode / Akamai, Hetzner, Google Cloud, OVH, Scaleway, and any KVM or bare-metal host running Ubuntu with a public IP.
Quick Start
1. Create your first app
The interactive wizard asks for a username, primary domain, Git repository URL (SSH format) and
branch
when applicable, and PHP version. Laravel apps always require a repository; for
custom
apps you can leave the repository empty to host the site via SFTP only — see
custom apps. Since v5.0, Laravel apps can use
classic PHP-FPM or Laravel Octane (FrankenPHP) with
--octane — see Laravel Octane.
$ cipi app create $ cipi app create --octane # Laravel Octane (FrankenPHP)
Or pass all flags directly to skip interactive mode:
$ cipi app create \
--user=myapp \
--domain=myapp.com \
--repository=git@github.com:you/myapp.git \
--branch=main \
--php=8.5
At the end, Cipi prints a credentials summary — save it, shown only once — including
the server's public IP address (for DNS configuration), the SSH deploy key, database credentials,
a ready-to-use mariadb+ssh:// connection URL for GUI clients (TablePlus, DBeaver,
Sequel Pro), webhook URL, and webhook token.
2. Add the deploy key to your Git provider
If you have configured a token for your Git provider via cipi git — GitHub, GitLab, Cursor
Origin, AWS CodeCommit, Bitbucket Cloud or Azure DevOps since v5.2.3 — this step is
automatic — Cipi adds the deploy key and creates the webhook for you. See
Git auto-setup for details.
Otherwise, copy the ssh-ed25519 ... key shown after app creation and add it as a
Deploy Key in your repository:
- GitHub: Repository → Settings → Deploy keys → Add deploy key
- GitLab: Repository → Settings → Repository → Deploy keys
3. Prepare your Laravel project
Cipi uses the database driver for cache, sessions, and queues. Run these once inside your Laravel project, commit, and push the generated migrations:
$ php artisan cache:table $ php artisan session:table $ php artisan queue:table $ php artisan migrate
artisan migrate --force on every deploy. The cache,
session, and queue tables will be created on first deploy if you commit the migrations.4. Deploy
$ cipi deploy myapp
Deployer clones your repo into a new releases/N/ directory, runs
composer install --no-dev, links .env and storage/, runs
migrations, runs artisan optimize, creates storage:link, swaps the
current symlink atomically, and restarts queue workers. Zero downtime.
5. Install SSL
$ cipi ssl install myapp
Certbot provisions a Let's Encrypt certificate, configures Nginx for HTTPS, and updates
APP_URL in .env. Your Laravel app is now live on
https://myapp.com.
Tech Stack
Cipi brings a complete, production-ready stack to your Ubuntu server. Here is everything that gets installed and configured:
| Component | Role |
|---|---|
| Ubuntu | Base OS (24.04 or 26.04 LTS) |
| Nginx | Web server, reverse proxy, SSL termination. Since v4.5.9, mainline
nginx from nginx.org
(1.29.8+) with max_headers 1000 for HTTP/2 hardening — not the Ubuntu
archive package |
| PHP-FPM | Default PHP runtime per app (8.3–8.5; ondrej/php, packages.sury.org, or Ubuntu main per release) |
| Laravel Octane | Optional FrankenPHP HTTP server per Laravel app (since v5.0) —
cipi app create --octane; FPM and Octane apps coexist on the same server
|
| MariaDB | Default relational database (drop-in MySQL replacement, port 3306). Optional
PostgreSQL via cipi db install pgsql (port 5432) since v4.8.0 |
| Valkey | In-memory store for cache, sessions, queues, broadcast (BSD-licensed Redis fork) |
| Supervisor | Process manager for Laravel queue workers, Octane, Horizon, and Reverb |
| Deployer | Zero-downtime deployment tool |
| Certbot | Let's Encrypt SSL certificates |
| UFW | Firewall (ports 22, 80, 443) |
| Fail2ban | Progressive brute-force protection with recidive jail |
| unattended-upgrades | Automatic security patches |
| Composer | PHP dependency manager |
| cipi CLI | Server management and orchestration |
What every app gets
Each app runs in a fully isolated environment. Laravel is the default — zero-downtime
deploys, its own database, workers, scheduler and webhook — optionally served by
Octane (FrankenPHP) instead of PHP-FPM. --custom is for simple sites
(WordPress, static + PHP): a classic deploy into htdocs, a configurable docroot, no
database, no .env, no cron, no workers.
| Area | What Cipi gives you |
|---|---|
| Web server | Nginx with a per-app virtual host — PHP-FPM or Octane via proxy_pass,
tuned for Laravel |
| PHP & Composer | Selectable per app, hot-swappable; settings managed with
cipi ini across FPM and
CLI |
| Database | MariaDB by default, optional PostgreSQL — a dedicated database and user per Laravel app |
| Queue workers | Supervisor pools per app — queue:work or Horizon; optional Reverb for
WebSockets |
| Deployments | Deployer — atomic symlink, 5 releases, rollback, optional Node build; classic clone
into htdocs for custom apps |
| SSL | Let's Encrypt via Certbot — HTTP-01 by default, optional DNS-01 (Cloudflare) with wildcards |
| Security | Fail2ban + UFW, a dedicated Linux user, its own FPM/Octane process and SSH key per app; opt-in CrowdSec and Cloudflare Zero Trust |
| Healthchecks | HTTP probes every 5 minutes, plus a
post-deploy check with optional
automatic rollback of a broken release; since 5.3,
cipi monitor watches
disk, services, workers, 5xx spikes and load |
| Backups | Backup profiles — what, how often, where, how long — S3 / S3-compatible / local, with client-side encryption |
| Configuration | Optional per-project cipi.yml for
aliases, databases, workers, healthcheck and backups |
App Structure
When you run cipi app create, Cipi creates a fully isolated environment for the app.
Here is everything that gets set up:
/home/myapp/ ← isolated Linux user (chmod 750) ├── .ssh/ │ ├── id_ed25519 ← deploy key (private) │ └── id_ed25519.pub ← deploy key (public — add to Git) ├── .deployer/ │ └── deploy.php ← Deployer config (auto-generated) ├── current -> releases/3/ ← symlink to active release ├── releases/ │ ├── 1/ │ ├── 2/ │ └── 3/ ← latest release (last 5 kept) ├── shared/ │ ├── .env ← auto-compiled by Cipi │ └── storage/ ← persistent storage └── logs/ ├── nginx-access.log ├── nginx-error.log ├── php-fpm-error.log ├── worker-default.log └── deploy.log
In addition to the home directory, Cipi creates these system files:
PHP-FPM pool: /etc/php/8.5/fpm/pool.d/myapp.conf (user=myapp; omitted for Octane apps) Octane (v5+): Supervisor myapp-octane + Nginx proxy_pass → 127.0.0.1:81xx Nginx vhost: /etc/nginx/sites-available/myapp Supervisor: /etc/supervisor/conf.d/myapp.conf Crontab: * * * * * php artisan schedule:run MariaDB: database 'myapp', user 'myapp'@'localhost'
The .env is auto-compiled with all credentials — database name, password, webhook token,
and APP_KEY. You never have to touch it manually, but you can always edit it with
cipi app env myapp.
If someone unpacks a zip as root or chmods .ssh to 775,
cipi app fix-permissions (since v5.2.1) restores this layout. A blunt
chown -R app:app is wrong: nginx writes vhost logs as www-data into
logs/ (2775 setgid), the home must stay 750 so
www-data (in the app group) can read the docroot, and .ssh must be
700 or OpenSSH StrictModes refuses the deploy key.