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

bash
# 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.

Run the installer on the server, not on your own machine. SSH into the fresh VPS first and run it there, as a user with 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:

bash
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:

bash
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:

bash
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
Only the .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:

bash
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:

bash
SSH_PUBKEY="ssh-ed25519 AAAA..." wget -O - https://cipi.sh/setup.sh | bash

AWS (root login disabled by default)

bash
$ 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.

Save the SSH access details, root password, and MariaDB root password. Shown only once during installation. Credentials are stored encrypted at /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, connect as cipi and either prefix them with sudo (allowed without a password, for cipi only) or become root with su root and the root password from the installation summary. Do not use sudo -s: the cipi user's own password is random and never shown, so sudo has nothing to accept for anything but cipi.

bash
# 1. connect to the server (key-based auth only)
$ ssh cipi@your-server-ip

# 2a. run a cipi command — no password needed
cipi@server:~$ sudo cipi status

# 2b. or become root (root password from the installation summary)
cipi@server:~$ su root

# 3. as root, every cipi command works without sudo
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 -s on 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
  • A VPS or VM with full virtualization (KVM, VMware, Hyper-V, Xen HVM) or a bare-metal server — not OpenVZ, LXC containers, or shared-kernel environments (including local Docker, Docker Desktop, Podman, OrbStack, Colima, WSL, and similar desktop container runtimes). Cipi sets up the firewall, fail2ban, swap and kernel parameters, which needs a kernel of its own.

Since v5.5.0 the installer checks the environment before anything else. In a container it stops and names what it found — OpenVZ / Virtuozzo, LXC, Docker, Podman, WSL, OrbStack, systemd-nspawn, or the raw type — says why Cipi cannot run there, and what to use instead. (Before, a container image without bc failed the Ubuntu version check with a confusing requires Ubuntu 24.04+ (found: 24.04); the version check no longer needs bc, so a minimal image of a supported release on a real VM passes.) Full VMs and bare metal are not affected.

Cipi does not run locally. It is not compatible with local dockerized environments. The installer may appear to succeed on a laptop VM or container, but SSL provisioning, domain routing, and production workflows will not work without a real public server. See Why not local? for the full explanation.

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.

bash
$ cipi app create
$ cipi app create --octane   # Laravel Octane (FrankenPHP)

Or pass all flags directly to skip interactive mode:

bash
$ 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. A Laravel 11+ skeleton already ships those migrations; on an older project run these once, commit, and push the generated migrations:

bash
$ php artisan cache:table
$ php artisan session:table
$ php artisan queue:table
$ php artisan migrate
Cipi automatically runs artisan migrate --force on every deploy. The cache, session, and queue tables will be created on first deploy if you commit the migrations.

Why not Valkey (Redis) by default? Cipi installs Valkey on every server, but it is one instance with one password for all the apps of the server: an app that has it can read, and flush, the cache, sessions and queues of every other app. The database driver keeps each app inside its own database and user. Valkey is a deliberate choice per app — faster queues and cache, Horizon, Reverb scaling — and how to switch, with a distinct REDIS_PREFIX per app, is in Valkey. The phpredis extension is already installed.

4. Deploy

bash
$ 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

bash
$ cipi ssl install myapp

Certbot obtains a Let's Encrypt certificate for the domain and its aliases, Cipi writes the HTTPS vhost (HTTP redirects to HTTPS) and updates APP_URL in .env. Your Laravel app is now live on https://myapp.com. The DNS of every name must already point to the server and port 80 must be open.

Behind the Cloudflare proxy, prefer cipi ssl install myapp --dns=cloudflare (works with the proxy on) and set SSL/TLS to Full (strict) — never Flexible. Subdomains for tenants (*.myapp.com): see Wildcard domains & SSL.

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.