Advanced
cipi api
Cipi can optionally enable a REST API layer on the server via cipi api
<domain>. It is powered by the Laravel package
cipi/api
(current release 1.11.11), which exposes:
- REST API — apps, aliases, deploy, SSL, databases, app logs, server status
(
/api/*) - MCP server — 29 tools at
/mcp(Streamable HTTP) - Swagger UI — interactive reference at
/docs
Requires PHP 8.2+ and Laravel 12+ on the panel host. This is
server-level automation — distinct from the per-app
Cipi Agent package (cipi/agent on each Laravel app).
The cipi/api package
On a normal Cipi server you never install the package manually — cipi api <domain>
provisions Laravel at /opt/cipi/api, Nginx, SSL, SQLite job queue, and
cipi-queue.service. For reference or custom setups:
$ composer require cipi/api $ php artisan vendor:publish --tag=cipi-config $ php artisan vendor:publish --tag=cipi-assets $ php artisan migrate $ php artisan cipi:seed-api-user $ php artisan cipi:token-create
Panel .env uses CIPI_APPS_JSON=/etc/cipi/apps.json (or
apps-public.json projection for non-sensitive fields). Token abilities are defined in
config/cipi.php — list them with php artisan cipi:token-abilities (same
list as cipi api token create since Cipi 4.6.3).
Source and changelog: github.com/cipi-sh/api (MIT). Client wrapper: cipi-cli.
Commands
$ cipi api <domain> # configure API at root (e.g. api.myhosting.com) $ cipi api ssl # install Let's Encrypt certificate for API domain $ cipi api token list # list tokens $ cipi api token create # create a new token (choose abilities) $ cipi api token revoke <id> # revoke a token $ cipi api status # Laravel + cipi-api versions, queue worker, pending jobs, FPM pool $ cipi api fix-permissions # repair panel storage/database ownership (www-data) $ cipi api update # soft update: composer update on Laravel and API packages $ cipi api upgrade # full rebuild with rollback at /opt/cipi/api.old
Panel API troubleshooting
After cipi self-update, root-owned files under /opt/cipi/api can prevent
PHP-FPM (www-data) from writing logs or the SQLite job database — the browser shows a
bare HTTP 500 on /docs or /mcp. Cipi normally repairs
ownership automatically during self-update; if problems persist:
$ cipi api fix-permissions # chown storage, database, bootstrap/cache, .env → www-data $ cipi api status # confirm Laravel version, queue worker, pending jobs
cipi api status prints the installed Laravel and cipi/api package versions,
whether cipi-queue.service is active, pending async jobs in the panel SQLite database,
and PHP-FPM pool stats for the API vhost (including slow requests when configured).
Token creation and granular permissions
Authentication uses Sanctum. Each token can have one or more abilities that limit allowed operations:
apps-view— read appsapps-create— create appsapps-edit— edit apps (PHP, repository, branch, primary domain since API 1.9.0+ / Cipi 4.6.2+)apps-suspend— suspend and unsuspend appsapps-basicauth— enable, disable, and inspect HTTP Basic Auth on apps (API 1.10.0+)apps-delete— delete appsdeploy-manage— deploy, rollback, unlockssl-manage— install and manage SSL certificatesaliases-view— read aliasesaliases-create— add aliasesaliases-delete— remove aliasesdbs-view— list databasesdbs-create— create databasesdbs-delete— delete databasesdbs-manage— backup, restore, regenerate passwordstatus-view— read server status snapshot (GET /api/status, API 1.11.6+)mcp-access— access the MCP server
Since Cipi 4.6.3 / API 1.11.7+,
cipi api token create reads the canonical ability list from the panel API package
(same entries as php artisan cipi:token-abilities on the server). Migration
4.6.3 retrofits existing servers with the updated list (including
status-view, apps-suspend, and apps-basicauth).
REST endpoints
All endpoints require the Authorization: Bearer <token> header. Write operations
(create, edit, delete, deploy, rollback, unlock, SSL, alias, database) are asynchronous: they return
202 Accepted with a job_id to poll via GET /api/jobs/{id}.
Read-only endpoints such as GET /api/dbs, GET /api/status, and
GET /api/apps/{name}/logs (API 1.11.9+) are synchronous.
POST /api/apps accepts optional custom (boolean) and
docroot (string) parameters for creating
custom apps with classic deployment.
The repository field is required for Laravel apps and
optional for custom apps: omit it (or send empty) to provision an SFTP-only site
aligned with Cipi v4.5.1+. When no repository is set, branch is
omitted.
The MCP tool AppCreate follows the same rules. Update the API package to
1.6.1+ on the server (cipi api update / cipi api upgrade)
so validation and OpenAPI match this behavior.
POST /api/apps/{name}/suspend takes an app offline by swapping its Nginx vhost for a
generic HTTP 503 maintenance page (HTTPS included) without deleting it, while
POST /api/apps/{name}/unsuspend restores the normal vhost. Both require the
apps-suspend ability and return 409 if the app is already in the target
state. The suspended flag survives vhost regeneration and is exposed on
GET /api/apps and GET /api/apps/{name}. These endpoints require the API
package 1.8.1+ and Cipi 4.5.8+ on the server.
PUT /api/apps/{name} accepts an optional domain field to rename the
app’s primary domain. The API validates the format synchronously and returns
409 if the domain is already used by another app (aliases of the current app are
allowed, so promoting an alias to primary works). Requires the API package 1.9.0+
and Cipi 4.6.2+. The MCP tool AppEdit accepts the same
domain parameter.
GET /api/apps and GET /api/apps/{name} expose boolean
suspended and basic_auth flags per app (from apps.json).
HTTP Basic Auth endpoints under /api/apps/{name}/basicauth/* wrap
cipi basicauth synchronously — they do not return a job_id. Enable accepts
optional user and password (auto-generated when omitted; returned once in
the response). Requires the apps-basicauth ability and API package
1.10.0+. This is distinct from Composer auth.json management — see
cipi basicauth.
GET /api/dbs lists databases synchronously by running
sudo cipi db list on the host (same as the server CLI). Other
/api/dbs/* write operations are asynchronous jobs. Database commands require Cipi
4.4.17+ on the server (cipi db … entries in the API sudoers
whitelist).
GET /api/status returns the same structured JSON as cipi status (system,
resources, services, PHP pools, app count). Since API 1.11.8+ the endpoint prefers
sudo cipi status on the host and falls back to direct host reads when sudo is
unavailable. Requires the status-view ability (API 1.11.6+). The MCP
tool ServerStatus returns the same payload and requires only
mcp-access.
GET /api/apps/{name}/logs returns synchronous, paginated log snapshots for nginx,
PHP-FPM, Laravel (when present), worker, and deploy logs — the REST counterpart to
cipi app logs and
cipi-cli apps logs. Query params:
type (default all), page (default 1, most
recent first), per_page (default 50, max 1000). Requires the
apps-view ability and API package 1.11.9+. Log text is redacted for
common secrets (same policy as MCP AppLogs since API 1.11.5+).
| Method | Endpoint | Required ability |
|---|---|---|
| GET | /api/apps |
apps-view |
| GET | /api/apps/{name} |
apps-view |
| GET | /api/apps/{name}/logs |
apps-view |
| POST | /api/apps |
apps-create |
| PUT | /api/apps/{name} |
apps-edit |
| POST | /api/apps/{name}/suspend |
apps-suspend |
| POST | /api/apps/{name}/unsuspend |
apps-suspend |
| DELETE | /api/apps/{name} |
apps-delete |
| GET | /api/apps/{name}/aliases |
aliases-view |
| POST | /api/apps/{name}/aliases |
aliases-create |
| DELETE | /api/apps/{name}/aliases |
aliases-delete |
| POST | /api/apps/{name}/deploy |
deploy-manage |
| POST | /api/apps/{name}/deploy/rollback |
deploy-manage |
| POST | /api/apps/{name}/deploy/unlock |
deploy-manage |
| POST | /api/apps/{name}/ssl |
ssl-manage |
| GET | /api/apps/{name}/basicauth |
apps-basicauth |
| POST | /api/apps/{name}/basicauth/enable |
apps-basicauth |
| POST | /api/apps/{name}/basicauth/disable |
apps-basicauth |
| GET | /api/dbs |
dbs-view |
| POST | /api/dbs |
dbs-create |
| DELETE | /api/dbs/{name} |
dbs-delete |
| POST | /api/dbs/{name}/backup |
dbs-manage |
| POST | /api/dbs/{name}/restore |
dbs-manage |
| POST | /api/dbs/{name}/password |
dbs-manage |
| GET | /api/status |
status-view |
| GET | /api/jobs/{id} |
any authenticated token |
REST examples (curl)
Set your API base URL and token (from cipi api token create):
export CIPI_API_URL="https://api.myserver.com" export CIPI_API_TOKEN="your-sanctum-token"
List apps (sync, 200):
curl -sS "${CIPI_API_URL}/api/apps" \ -H "Authorization: Bearer ${CIPI_API_TOKEN}" \ -H "Accept: application/json"
Server status (sync, requires status-view):
curl -sS "${CIPI_API_URL}/api/status" \ -H "Authorization: Bearer ${CIPI_API_TOKEN}"
App logs (sync, requires apps-view, API 1.11.9+):
curl -sS "${CIPI_API_URL}/api/apps/myapp/logs?type=deploy&page=1&per_page=50" \ -H "Authorization: Bearer ${CIPI_API_TOKEN}" \ -H "Accept: application/json"
Deploy an app (async, 202 + job_id):
curl -sS -X POST "${CIPI_API_URL}/api/apps/myapp/deploy" \ -H "Authorization: Bearer ${CIPI_API_TOKEN}" \ -H "Accept: application/json" # poll until completed curl -sS "${CIPI_API_URL}/api/jobs/JOB_ID" \ -H "Authorization: Bearer ${CIPI_API_TOKEN}"
Database backup (async, dbs-manage) — returns a real backup path in
the job result, not an anonymized dump (see
Agent anonymizer):
curl -sS -X POST "${CIPI_API_URL}/api/dbs/myapp_db/backup" \ -H "Authorization: Bearer ${CIPI_API_TOKEN}"
Host integration (sudoers)
The panel API runs as www-data and executes Cipi CLI commands via
sudo using /etc/sudoers.d/cipi-api — an explicit whitelist of
cipi subcommands. Vault and MariaDB credentials stay inside Cipi, not in PHP.
GET /api/dbs— runssudo cipi db list(sync). Requires Cipi 4.4.17+ (migration addscipi db …to sudoers). Without it:sudo: a terminal is required.GET /api/status/ MCPServerStatus— prefersudo cipi status(API 1.11.8+); host-read fallback when sudo fails.- MCP
ServiceList—sudo cipi service list - MCP
AppArtisan—sudo cipi app artisan <app> … - Async jobs —
cipi app,deploy,alias,ssl,db create|delete|backup|restore|password, etc.
After cipi self-update, run cipi api fix-permissions if
/docs or /mcp return HTTP 500 (see troubleshooting above).
Swagger / OpenAPI
Interactive documentation is available at /docs (Swagger UI). The OpenAPI spec is
generated from public/api-docs/openapi.json and covers apps (including suspend,
unsuspend, domain rename, basic auth, and paginated logs), aliases, deploy, SSL, databases, server
status, job polling with structured result types, and MCP tool schemas. Current API
package version: 1.11.11.
MCP server
An MCP (Model Context Protocol) server is exposed at /mcp via
Streamable HTTP. Since API package 1.11.1+, a token with the
mcp-access ability is sufficient for all MCP tools — per-endpoint REST
abilities (apps-view, deploy-manage, apps-basicauth, etc.)
are not checked on /mcp. The server exposes 29 tools for app, alias,
database, deploy, SSL, HTTP Basic Auth, job polling, logs, Artisan, and server monitoring. Write
operations that dispatch async jobs return a job_id — poll with JobShow
(API 1.11.0+). Basic auth actions and read-only tools run synchronously.
- Applications:
AppList,AppShow,AppCreate,AppEdit,AppSuspend,AppUnsuspend,AppDelete,AppDeploy,AppDeployRollback,AppDeployUnlock,AppArtisan(Laravel apps only; rejects custom apps andtinker) - HTTP Basic Auth:
AppBasicAuthStatus,AppBasicAuthEnable,AppBasicAuthDisable - Aliases:
AliasList,AliasAdd,AliasRemove - Databases:
DbList,DbCreate,DbDelete,DbBackup,DbRestore,DbPassword - SSL:
SslInstall - Jobs & logs:
JobShow(poll async job status, parsedresult, and CLI output),AppLogs(recent app logs by type:all,nginx,php,worker,deploy,laravel— same ascipi app logs; REST equivalent:GET /api/apps/{name}/logssince API 1.11.9+),ApiLogShow(recent Laravel logs for the panel API host) - Server monitoring:
ServerStatus(structured JSON matchingGET /api/status/cipi status),ServiceList(system service status viacipi service list)
AppLogs,
ApiLogShow) prefix every response with a production-content warning and redact
common secrets before delivery. Sensitive CLI output from JobShow and
AppArtisan is redacted too; structured job result objects (e.g. app
credentials from create jobs) are left intact so operators can still read them once.Since Cipi 4.6.3, the panel API package is soft-updated nightly at
04:30 via /etc/cron.d/cipi-api (cipi api update), so MCP
and REST endpoints stay current without manual intervention.
Installing the MCP server
The MCP endpoint is optional and loads only when the required MCP package is installed. To use it from VS Code, Cursor, or Claude Desktop:
- Configure the API with
cipi api <domain>andcipi api ssl - Create a token with
cipi api token createand select at leastmcp-access - Add the MCP server to your client config (see below)
Cursor
Add to ~/.cursor/mcp.json (or Cursor → Settings → MCP):
{
"mcpServers": {
"cipi-api": {
"type": "http",
"url": "https://<your-api-domain>/mcp",
"headers": {
"Authorization": "Bearer <your-token>"
}
}
}
}
Cursor connects natively over HTTP — no bridge needed.
VS Code
VS Code (with GitHub Copilot) supports MCP natively since 1.102. Add to .vscode/mcp.json
or run MCP: Open User Configuration for a global setup. Use
inputs to prompt for the token once and store it securely:
{
"inputs": [
{
"type": "promptString",
"id": "cipi-token",
"description": "Cipi API Token",
"password": true
}
],
"servers": {
"cipi-api": {
"type": "http",
"url": "https://<your-api-domain>/mcp",
"headers": {
"Authorization": "Bearer ${input:cipi-token}"
}
}
}
}
Restart VS Code after saving. Use MCP: Add Server from the Command Palette for a guided setup.
Claude Code
Add the MCP server directly from the CLI:
$ claude mcp add --transport http cipi-api https://<your-api-domain>/mcp \
--header "Authorization: Bearer <your-token>"
Claude Desktop
Claude Desktop requires the mcp-remote bridge to convert stdio to HTTP. Add to
~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or the
equivalent
config path on your OS:
{
"mcpServers": {
"cipi-api": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://<your-api-domain>/mcp",
"--header",
"Authorization: Bearer <your-token>"
]
}
}
}
Install mcp-remote once with npm install -g mcp-remote.
Replace <your-api-domain> with your API domain (e.g.
api.myhosting.com)
and <your-token> with the token created in step 2.
WHMCS Module
An official WHMCS provisioning module is available at github.com/cipi-sh/whmcs. It bridges the WHMCS provisioning lifecycle to the Cipi REST API — automating app creation, deletion, SSL certificates, deployments, and configuration changes for your hosting customers. No Composer dependencies; the module is a self-contained drop-in.
Requirements
- WHMCS 8.x (provisioning module type “Server”)
- Cipi server with API enabled:
cipi api <domain>andcipi api ssl - Sanctum Bearer token with the required abilities:
| Ability | Required for |
|---|---|
apps-view |
Test Connection, App Info |
apps-create |
Create Account |
apps-edit |
Change Package |
apps-suspend |
Suspend / Unsuspend |
apps-delete |
Terminate Account |
deploy-manage |
Deploy, Rollback, Unlock |
ssl-manage |
Install SSL, Auto-SSL |
Installation
- Copy
modules/servers/cipi/into your WHMCS root:your-whmcs/ └── modules/ └── servers/ └── cipi/ ├── cipi.php └── lib/ └── CipiApiClient.php - In WHMCS Admin → System Settings → Servers → Add New Server:
- Type: Cipi (Laravel hosting)
- Hostname: API base URL (e.g.
https://api.example.com, no trailing slash) - Password: Bearer token from
cipi api token create - Secure: Yes (recommended — enables TLS verification)
- Create a hosting product linked to this server and configure Module Settings:
| Setting | Description | Default |
|---|---|---|
| PHP Version | 8.2 / 8.3 / 8.4 / 8.5 | 8.5 |
| App Type | laravel or custom |
laravel |
| Git Repository (SSH) | Required for Laravel; optional for custom | — |
| Git Branch | Branch to deploy | main |
| Auto SSL | Install Let’s Encrypt after creation | No |
App types
| App type | Cipi equivalent | Stack | Git / Deploy |
|---|---|---|---|
| laravel (default) | cipi app create |
Isolated Linux user, PHP-FPM pool, Nginx vhost, MariaDB, Supervisor workers, Deployer releases | SSH repository URL required; branch from settings |
| custom | cipi app create --custom |
htdocs/ directory, Nginx + PHP — ideal for static sites, SPAs, WordPress, or generic PHP apps | Optional: leave Git Repository empty for SFTP-only hosting, or set a repo for Git-based deploy |
Provisioning lifecycle
| WHMCS action | API call | Behaviour |
|---|---|---|
| Test Connection | GET /api/apps |
Validates token and API reachability |
| Create Account | POST /api/apps |
Provisions a Cipi app (Laravel or custom); waits for async jobs; optionally installs SSL |
| Suspend | POST /api/apps/{name}/suspend |
Takes the app offline (HTTP 503 maintenance page) without deleting it; waits for async jobs |
| Unsuspend | POST /api/apps/{name}/unsuspend |
Restores the app’s normal Nginx vhost; waits for async jobs |
| Terminate Account | DELETE /api/apps/{name} |
Removes the app; waits for async jobs |
| Change Package | PUT /api/apps/{name} |
Updates PHP version, Git repository, or branch |
Suspend / Unsuspend require Cipi 4.5.8+ (suspend/unsuspend
endpoints), the API package 1.8.1+, and a token with the apps-suspend
ability. Suspending swaps the app’s vhost for a generic HTTP 503 maintenance page;
unsuspending restores it.
Admin buttons
From the WHMCS admin service view, operators can trigger one-click actions:
| Button | API call | Description |
|---|---|---|
| Install SSL | POST /api/apps/{name}/ssl |
Install a Let’s Encrypt certificate |
| Deploy | POST /api/apps/{name}/deploy |
Trigger a zero-downtime deployment |
| Rollback Deploy | POST /api/apps/{name}/deploy/rollback |
Revert to the previous release |
| Unlock Deploy | POST /api/apps/{name}/deploy/unlock |
Unlock a stuck deployment |
| App Info | GET /api/apps/{name} |
Fetch current app details into the Module Log |
Auto-SSL on creation
Enable Auto SSL in the product module settings to automatically install a Let’s Encrypt certificate right after provisioning. If SSL installation fails, the app is still created successfully and a warning is logged.
Full API client
The bundled CipiApiClient covers the entire Cipi REST API surface. Even if a feature
is not wired to a WHMCS hook, you can use the client in custom hooks or addons:
| Area | Methods |
|---|---|
| Apps | listApps, getApp, createApp, editApp, suspendApp, unsuspendApp, deleteApp |
| Deploy | deployApp, rollbackDeploy, unlockDeploy |
| SSL | installSsl |
| Aliases | listAliases, addAlias, removeAlias |
| Databases | listDatabases, createDatabase, deleteDatabase, backupDatabase, restoreDatabase, resetDatabasePassword |
| Jobs | getJob, waitForJob |
Extending the module
// Example: add an alias from a WHMCS hook require_once ROOTDIR . '/modules/servers/cipi/lib/CipiApiClient.php'; $client = new CipiApiClient('https://api.example.com', $token); $client->addAlias('myapp', 'alias.example.com'); // Example: create an extra database $client->createDatabase('myapp_extra'); // Example: backup a database $client->backupDatabase('myapp');
Customer-facing behaviour
The module does not add a Client Area tab, custom buttons, or live status from Cipi. Customers see the standard WHMCS service view (domain, status, renewal dates). When Cipi provisions an app it generates one-time secrets (SSH password, DB password, deploy key, webhook URL). The REST API does not automatically push those secrets into WHMCS — you should extend the module, write a hook, or deliver credentials through your support workflow.
Module logging
All API calls are logged via logModuleCall() — Create, Terminate, Change Package,
SSL, Deploy, Rollback, Unlock, and App Info. Enable Utilities → Logs → Module
Log in WHMCS Admin for full visibility.
cipi sync
Transfer, replicate, and back up entire Laravel applications between Cipi servers — including configuration, database dumps, storage files, SSH keys, workers, and crontabs. Every archive is encrypted with AES-256-CBC and protected by a user-defined passphrase, so credentials and sensitive data are safe at rest and during transfer.
Commands overview
$ cipi sync export [app ...] [--with-db] [--with-storage] [--output=<path>] [--passphrase=<secret>] $ cipi sync import <archive.tar.gz.enc> [app ...] [--update] [--deploy] [--yes] [--passphrase=<secret>] $ cipi sync push [app ...] [--host=IP] [--port=22] [--with-db] [--with-storage] [--import] [--passphrase=<secret>] $ cipi sync list <archive.tar.gz.enc> [--passphrase=<secret>] $ cipi sync pubkey # display the server's sync public key for inter-server trust $ cipi sync trust # add a remote server's public key to cipi's authorized_keys
Archive encryption
All sync archives are encrypted by default with AES-256-CBC. During export you are
prompted for a passphrase (minimum 8 characters) that protects the archive. The same passphrase is
required to import or inspect it. This protects SSH keys, .env files, database dumps,
and credentials at rest and during transfer.
# Interactive mode (default) — prompted for passphrase $ cipi sync export --with-db # Enter passphrase to encrypt the archive: ******** # Confirm passphrase: ******** # Non-interactive mode — for cron jobs and scripts $ cipi sync export --with-db --passphrase="MyStr0ngP@ss"
echo "MyStr0ngP@ss" > /etc/cipi/.sync_passphrase && chmod 400
/etc/cipi/.sync_passphrase. Then use
--passphrase="$(cat /etc/cipi/.sync_passphrase)" in cron jobs.
Export
Packs app configs into an encrypted .tar.gz.enc archive. Optionally includes database
dumps and storage files.
# Export all apps (config only) $ cipi sync export # Export three specific apps with database + storage $ cipi sync export shop blog api --with-db --with-storage # Export to a custom path (non-interactive) $ cipi sync export --with-db --output=/root/backups/cipi-march.tar.gz --passphrase="MyStr0ngP@ss"
What goes into the archive
| File | Description | Included |
|---|---|---|
env |
The app's .env from /home/<app>/shared/.env |
Always |
auth.json |
Composer auth credentials (if exists) | Always |
deploy.php |
Deployer config | Always |
ssh/* |
Deploy key, known_hosts, authorized_keys, SSH config | Always |
supervisor.conf |
Queue workers config | Always |
crontab |
App's crontab (scheduler + deploy trigger) | Always |
db.sql.gz |
Gzipped MariaDB dump (schema + data + routines) | --with-db |
storage.tar.gz |
Archive of /home/<app>/shared/storage/ |
--with-storage |
Plus global configs: apps.json (filtered to selected apps), databases.json,
backup.json, api.json.
Import
Restores apps from an archive onto the current server.
# Import all apps from archive $ cipi sync import /tmp/cipi-sync-aws01-20260306.tar.gz.enc # Import only two apps from an archive that contains ten $ cipi sync import /tmp/cipi-sync-aws01-20260306.tar.gz.enc shop blog --passphrase="MyStr0ngP@ss" # Import and deploy code from Git immediately $ cipi sync import /tmp/cipi-sync-aws01-20260306.tar.gz.enc --deploy # Non-interactive (skip all prompts) $ cipi sync import /tmp/cipi-sync-aws01-20260306.tar.gz.enc --yes --passphrase="MyStr0ngP@ss"
What import does for a NEW app
When an app does not exist on the target server, import creates it from scratch — equivalent to
cipi app create with all configs pre-filled from the archive:
- Linux user — Creates a new user with a random password
- Directories — Creates
/home/<app>/shared/,logs/,.ssh/,.deployer/ - SSH deploy key — Restores from archive (same key works with GitHub/GitLab without reconfiguration)
- MariaDB database — Creates database + user with a new random password
- Database data — Imports the dump if
--with-dbwas used during export .env— Copies from archive, then overwritesDB_PASSWORD,DB_USERNAME,DB_DATABASE,DB_HOSTwith the new server's values. Everything else (APP_KEY,MAIL_*,REDIS_*, custom vars) stays as-is- PHP-FPM pool, Nginx vhost, Supervisor workers, Crontab, Deployer — Fully configured from archive data
Safety checks before import
The import runs pre-flight checks before touching anything:
- App already exists — blocked unless
--updateis passed - Domain conflict — blocked if another app already uses the same domain
- Missing PHP version — warning (the app is skipped; install the version first
with
cipi php install)
Update mode (--update)
The key feature for repeated sync (e.g. failover replication). Without
--update, import refuses to touch apps that already exist. With --update,
it
updates existing apps and creates new ones.
$ cipi sync import /tmp/archive.tar.gz.enc --update --passphrase="MyStr0ngP@ss"
What update does for an existing app
.envsync — The archive.envreplaces the local one, butDB_PASSWORD,DB_USERNAME,DB_DATABASE, andDB_HOSTare preserved from the local server. Everything else (APP_KEY,MAIL_*,REDIS_*, custom vars) comes from the source.- Database data — If the archive has a dump, drops all tables (with
SET FOREIGN_KEY_CHECKS=0) and reimports. Uses local root credentials. - Storage — If the archive has storage, extracts over the existing directory (new files added, existing overwritten).
- PHP version migration — If the source uses a different PHP version, the update
migrates FPM pool, supervisor, crontab, deployer, and
.envautomatically. - Nginx vhost, Supervisor workers, Deployer config — Regenerated from archive data.
- Deploy — If
--deployis passed, runsdep deployto pull latest code.
What update does NOT change
- Linux user password
- SSH deploy keys (kept from first import)
- MariaDB user credentials (target keeps its own)
- SSL certificates (run
cipi ssl installseparately)
List (inspect archive)
View what is inside an archive without importing anything.
$ cipi sync list /tmp/cipi-sync-aws01-20260306.tar.gz.enc --passphrase="MyStr0ngP@ss" Cipi Sync Archive ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Cipi v4.7.12 Exported 2026-03-06T15:00:00Z Source aws01 (3.120.xx.xx) Database true Storage true Apps APP DOMAIN PHP DB STORAGE shop shop.example.com 8.4 yes yes blog blog.example.com 8.4 yes yes api api.example.com 8.5 yes yes ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Push (export + transfer + import)
Combines export, rsync transfer, and remote import in one command. Runs entirely from the source server.
# Interactive push — prompted for target IP and passphrase $ cipi sync push --with-db --with-storage --import # Non-interactive push (for cron and scripts) $ cipi sync push --host=51.195.xx.xx --port=22 --with-db --with-storage --import --passphrase="MyStr0ngP@ss" # Push specific apps only $ cipi sync push shop blog --host=51.195.xx.xx --with-db --import --passphrase="MyStr0ngP@ss" # Push without auto-import (transfer only — import manually on remote) $ cipi sync push --host=51.195.xx.xx --with-db --passphrase="MyStr0ngP@ss"
How push works
- Step 1: Runs
cipi sync exportlocally (encrypts with passphrase) - Step 2: Transfers the encrypted archive to the target via rsync
- Step 3: If
--importis passed, runscipi sync import --update --yeson the target via SSH
Push always adds --update and --yes when calling import on the remote. This
means: first run creates all apps, subsequent runs incrementally update them. This is what makes
push
safe to run repeatedly via cron.
SSH setup for push
The source server needs SSH access to the target as the cipi user. Use the built-in
trust mechanism for passwordless, key-based authentication between Cipi servers:
# On the SOURCE server — display its sync public key $ cipi sync pubkey # On the TARGET server — add the source's public key to cipi's authorized_keys $ cipi sync trust
Once trusted, cipi sync push connects as the cipi user automatically —
no root access required.
Practical scenarios
Scenario 1: Migrate all apps from AWS to OVH
You have 20 apps on AWS. You bought an OVH VPS and installed Cipi on it.
# On AWS (source server) $ cipi sync push --host=51.195.xx.xx --with-db --with-storage --import
On the OVH target: 20 Linux users, 20 databases, 20 nginx vhosts, PHP-FPM pools, supervisor configs,
crontabs — all created automatically. DB data imported, storage extracted, .env files
copied with OVH's DB passwords, SSH deploy keys preserved (same keys work with GitHub). After
import,
install SSL and update DNS:
# On OVH (target server) $ cipi ssl install shop $ cipi ssl install blog # ... then update DNS A records to OVH IP
Scenario 2: Scheduled failover replication (cron)
Every 6 hours, Server 1 syncs all apps to Server 2. If Server 1 dies, change DNS and go live on Server 2.
# One-time setup on Server 1 — trust Server 2 using cipi sync trust $ cipi sync pubkey # copy this key, then run "cipi sync trust" on Server 2 $ echo "YourStr0ngPassphrase!" > /etc/cipi/.sync_passphrase $ chmod 400 /etc/cipi/.sync_passphrase # First push (manual, to verify) $ cipi sync push --host=server2-ip --with-db --with-storage --import --passphrase="$(cat /etc/cipi/.sync_passphrase)" # Add to crontab for automatic replication $ crontab -e
0 */6 * * * /usr/local/bin/cipi sync push --host=51.195.xx.xx --port=22 --with-db --with-storage --import --passphrase="$(cat /etc/cipi/.sync_passphrase)" >> /var/log/cipi/sync-replica.log 2>&1
Data loss window equals the cron interval (6 hours in this example). When Server 1 goes down: change
DNS to Server 2, run cipi ssl install for each app, and you are live.
Scenario 3: Replicate to multiple servers
# Stagger by 30 minutes so exports don't run simultaneously
0 */6 * * * /usr/local/bin/cipi sync push --host=51.195.xx.xx --with-db --with-storage --import --passphrase="$(cat /etc/cipi/.sync_passphrase)" >> /var/log/cipi/sync-ovh.log 2>&1
30 */6 * * * /usr/local/bin/cipi sync push --host=164.90.xx.xx --with-db --with-storage --import --passphrase="$(cat /etc/cipi/.sync_passphrase)" >> /var/log/cipi/sync-do.log 2>&1
Scenario 4: Daily encrypted backup (no transfer)
0 3 * * * /usr/local/bin/cipi sync export --with-db --with-storage --output=/root/backups/cipi-$(date +\%Y\%m\%d).tar.gz --passphrase="$(cat /etc/cipi/.sync_passphrase)" >> /var/log/cipi/export.log 2>&1
Creates an encrypted portable archive every night. Restore on any Cipi server at any time with
cipi sync import.
Limitations
- SSL certificates are not included in the archive. Run
cipi ssl installafter importing on a new server. - DB sync is full replace, not incremental. Each update drops all tables and reimports.
- Storage sync is full extract, not incremental rsync. Deleted files on the source remain on the target.
- Deploy keys are the same on source and target — no GitHub/GitLab reconfiguration needed.
Vault & Encryption
Cipi encrypts all configuration files at rest using AES-256-CBC. The Vault system
provides transparent encryption and decryption so that sensitive data — database passwords, API
tokens,
SSH keys, .env contents — is never stored in plaintext on disk.
Architecture
The system is built on two layers:
- Vault — transparent encryption of JSON configuration files on disk
(
server.json,apps.json,databases.json,backup.json,smtp.json,api.json) - Sync encryption — passphrase-based encryption of export archives for secure transfer between servers
How Vault works
A master key is generated during installation with openssl rand -base64 32 and stored at
/etc/cipi/.vault_key (chmod 400, root-only). Every JSON config file is
encrypted on disk with openssl enc -aes-256-cbc -salt -pbkdf2. Files keep the
.json extension — the content is simply an encrypted blob instead of readable JSON.
The vault_read function auto-detects whether a file is plaintext or encrypted (backward
compatibility), so existing servers migrate seamlessly during update.
Vault functions
# Core functions in lib/vault.sh vault_init # Generate .vault_key if not present vault_read <file> # Decrypt and output JSON to stdout (auto-detect plain/encrypted) vault_write <file> # Read JSON from stdin, encrypt and write to disk vault_seal <file> # Encrypt an existing plaintext file in-place vault_get <file> <jq_query> # Shortcut: vault_read | jq
Public projection
Cipi generates an apps-public.json file containing only non-sensitive fields (domain,
aliases, PHP version, branch, repository, user, creation timestamp, plus
suspended and basic_auth flags). The cipi-api group reads
this plaintext projection instead of the encrypted file, keeping the vault key restricted to root.
Sync archive encryption
When you run cipi sync export, configs are decrypted from the vault into a staging area,
then the entire archive is encrypted with your passphrase. On import, the archive is decrypted with
the passphrase, and configs are re-encrypted with the destination server's vault
key.
chmod 400 and included in server backups. Consider exporting it manually for
additional safety.
Email Notifications
Cipi can send email alerts when backup errors, deploy failures, system cron job failures, or
security-relevant authentication events occur.
SMTP configuration is stored encrypted in /etc/cipi/smtp.json and included in sync
exports.
Commands
$ cipi smtp configure # interactive setup (Gmail, SendGrid, Mailgun, custom) $ cipi smtp status # display current notification settings $ cipi smtp test # send a verification email $ cipi smtp enable # enable notifications $ cipi smtp disable # disable without losing settings $ cipi smtp delete # remove SMTP configuration entirely
Granular notification triggers
Since v4.6.3, you can control which events send email when SMTP is configured.
All triggers are on by default; events are always logged to
/var/log/cipi/events.log regardless.
$ cipi notifications list # all triggers grouped by category $ cipi notifications enable <trigger> # turn one trigger on $ cipi notifications disable <trigger> # turn one trigger off $ cipi notifications enable-all # re-enable everything $ cipi notifications disable-all # mute all email alerts $ cipi notifications reset # restore defaults (all on)
Config: /etc/cipi/notifications.json. Run
cipi notifications list on the server for the live on/off state. Trigger IDs for
cipi notifications enable|disable <trigger>:
| Trigger ID | Category | Event |
|---|---|---|
app_create | Apps | App created |
app_edit | Apps | App modified |
app_delete | Apps | App deleted |
app_suspend | Apps | App suspended |
app_unsuspend | Apps | App unsuspended |
app_ssh_password_reset | Apps | App SSH password reset |
app_db_password_reset | Apps | App DB password reset |
alias_add | Domains | Alias added |
alias_remove | Domains | Alias removed |
auth_create | Auth | Composer auth.json created |
auth_edit | Auth | Composer auth.json edited |
auth_delete | Auth | Composer auth.json deleted |
basicauth_enable | Basic auth | HTTP basic auth enabled |
basicauth_disable | Basic auth | HTTP basic auth disabled |
deploy_success | Deploy | Deploy succeeded |
deploy_fail | Deploy | Deploy failed |
deploy_rollback | Deploy | Deploy rollback |
ssl_install | SSL | SSL certificate installed |
ssl_renew | SSL | SSL certificates renewed |
php_install | PHP | PHP version installed |
php_switch | PHP | System PHP switched |
php_remove | PHP | PHP version removed |
db_create | Database | Database created |
db_delete | Database | Database deleted |
worker_add | Workers | Worker added |
worker_remove | Workers | Worker removed |
ssh_key_add | SSH keys | SSH key added |
ssh_key_rename | SSH keys | SSH key renamed |
ssh_key_remove | SSH keys | SSH key removed |
ssh_login | Security | SSH login (cipi/root/sudo users) |
sudo | Security | Sudo elevation |
su | Security | su to root by cipi |
backup_fail | Backup | Backup failed |
cron_fail | Cron | Cron job failed |
reset_root_password | Reset | Root SSH password reset |
reset_db_password | Reset | MariaDB root password reset |
reset_valkey_password | Reset | Valkey password reset |
api_configure | API | Panel API configured |
api_update | API | Panel API updated |
api_upgrade | API | Panel API upgraded |
api_ssl | API | Panel API SSL installed |
git_configure | Git | Git provider token configured |
sync_export | Sync | Apps exported |
sync_import | Sync | Apps imported |
sync_push | Sync | Apps pushed to remote |
service_restart | Services | Service restarted |
service_start | Services | Service started |
service_stop | Services | Service stopped |
Automatic alerts
Once configured, Cipi sends email notifications on:
- Backup errors (S3 upload failures, dump errors)
- Deploy failures (Deployer errors, rollback triggers)
- System cron job failures (via the
cipi-cron-notifywrapper) - App lifecycle events — notifies when an app is created, edited, or deleted, including server hostname, app name, domain, and PHP version
- Sudo and su elevation — notifies when any user successfully elevates via
sudoorsu, including who ran it, target user (forsu), SSH key, client IP, and TTY - Privileged SSH login — notifies when
rootor any sudoer logs in via SSH, including source IP, SSH key fingerprint, and key comment - SSH key changes — notifies when an SSH key is added to, removed from, or renamed on the
cipiuser, including hostname, IP, fingerprint, key comment, timestamp, and remaining key count. Rename alerts also include the old and new key name.
Every email notification includes a footer with the client IP (SSH_CLIENT) and the SSH
key name used to authenticate, when applicable. The key name is resolved via
SSH_USER_AUTH with an auth.log fallback when needed.
Security auth notifications
Cipi integrates PAM-based authentication notifications via
pam_exec.so with ExposeAuthInfo enabled. When SMTP is configured, the
system automatically sends email alerts on these security-relevant events:
- Sudo and su elevation — triggered when any user successfully runs
sudoorsu. The notification includes the username, target user (forsu), the TTY, SSH key, client IP, and timestamp. - Privileged SSH login — triggered when
rootor any user in thesudogroup logs in via SSH. The notification includes the username, source IP address, SSH key fingerprint, and key comment (resolved from/var/log/auth.logfingerprint matching againstauthorized_keys). - SSH key changes — triggered when an SSH key is added to, removed from, or
renamed on the
cipiuser viacipi ssh add,cipi ssh remove, orcipi ssh rename. The notification includes the hostname, server IP, key fingerprint, key comment, timestamp, and remaining key count. Rename alerts also include the old and new key name. - App lifecycle events — triggered when an app is created, edited, or deleted. The notification includes the server hostname, app name, domain, and PHP version.
Notifications run asynchronously in the background so they never delay login or command execution. If SMTP is not configured, the hooks fail silently with no impact on the system.
Security event log
Regardless of SMTP configuration, all notification events (SSH key changes, app lifecycle,
password resets, sudo/su/SSH login, cron failures) are always logged to
/var/log/cipi/events.log in a compact one-line format. The log is rotated daily with
1-year retention via logrotate.
Cron wrapper
The cipi-cron-notify utility wraps system cron jobs and sends a notification if the job
exits with a non-zero code. This is useful for monitoring critical scheduled tasks.
Log Retention (GDPR)
Cipi enforces automatic log rotation policies designed to meet GDPR and general data protection requirements. Logs are rotated and deleted automatically — no manual cleanup needed.
| Category | Logs | Retention |
|---|---|---|
| Application | Laravel, PHP-FPM, workers, deploy, system | 12 months |
| Security | Fail2ban, UFW firewall, authentication, Cipi events (events.log) |
12 months |
| HTTP / Navigation | Nginx access and error logs | 90 days |
Valkey
Valkey is the in-memory data store that Cipi installs as part of the default stack.
Since v4.5.6 Cipi provisions Valkey instead of
redis-server. It excels at caching, session storage, message queues, real-time
broadcasting, and rate limiting.
Why Valkey instead of Redis
Valkey is the truly open-source, BSD-licensed fork of Redis, stewarded by the
Linux Foundation. It was created in 2024 after Redis Inc. relicensed Redis away
from the permissive BSD license to the source-available SSPL / RSALv2 — a change that no longer met
the open-source definition. Backed by AWS, Google Cloud, Oracle, and a large community, Valkey
continues the same battle-tested codebase under a license that stays free forever.
That makes it a perfect fit for Cipi's MIT, no-vendor-lock-in philosophy, and it ships
natively in Ubuntu 24.04's Universe repository (packages
valkey-server + valkey-tools) — no third-party PPA to trust.
Just as importantly, Valkey is a drop-in replacement: it speaks the exact same RESP
protocol on the same port (127.0.0.1:6379), honours the same
requirepass / bind directives, and reads the same RDB/AOF data format. Your
apps need zero changes — the phpredis extension and your existing
REDIS_* .env values keep working exactly as before.
How Cipi implements it
- Install —
setup.shinstalls and configures Valkey (/etc/valkey/valkey.conf, servicevalkey-server), bound tolocalhostonly and protected with a password. - Service management —
cipi service …managesvalkey-server(the namesredis-server,redis, andvalkeyare still accepted as aliases). It is added to the unattended-upgrades blacklist, so Cipi manages it instead of an automatic upgrade. - Credentials — stored under
valkey_user/valkey_passwordin/etc/cipi/server.json(the legacyredis_*keys are still read as a fallback). Host: 127.0.0.1, Port: 6379. - Password reset —
cipi reset valkey-passwordregenerates the password and restarts the service (cipi reset redis-passwordstays as an alias).
Migrating from Redis (4.5.6 / 4.5.7)
Existing servers are switched to Valkey automatically on cipi self-update — no app
.env editing required. The migration reuses the current Redis password (recovered from
server.json or /etc/redis/redis.conf), forces an RDB SAVE and
snapshots dump.rdb/AOF, purges redis-server, installs
valkey-server + valkey-tools on the same port with the same
requirepass / bind, restores the dataset, and rewrites
server.json (redis_* → valkey_*) and the unattended-upgrades
blacklist — so cache, sessions, and queued jobs survive the switch.
v4.5.7 corrects the package name to valkey-server (the Ubuntu 24.04
daemon package; 4.5.6 initially used valkey) and makes the migration fully
self-contained and safe. It auto-enables the universe APT component when the package
isn't found, runs a post-start health check (PING → PONG with the
password), and rolls back to redis-server — restoring both the saved
password and the dataset — if Valkey can't be installed or doesn't come up healthy. The dataset
snapshot is kept until the switch is verified, then cleaned up. The migration is idempotent: servers
already on Valkey skip it.
Laravel integration
Add these variables to your .env via cipi app env myapp. The variable names
stay REDIS_* — that is what phpredis and Laravel's redis
driver expect, and Valkey answers on the very same socket:
REDIS_HOST=127.0.0.1 REDIS_PASSWORD=your-password-from-server-json REDIS_PORT=6379
Then set the drivers for each use case:
- Cache —
CACHE_STORE=redis - Session —
SESSION_DRIVER=redis - Queue —
QUEUE_CONNECTION=redis(thencipi worker restart myapp) - Broadcasting —
BROADCAST_CONNECTION=redis
Install the phpredis PHP extension for best performance, or use
predis/predis as a pure-PHP fallback. Both talk to Valkey transparently.
Self-Update
Cipi can update itself from GitHub without affecting any app, database, or configuration.
$ cipi self-update --check # check for a new version $ cipi self-update # update to latest
Update process
- Downloads the latest version from GitHub
- Backs up the current installation to
/opt/cipi.bak.YYYYMMDDHHMMSS/ - Replaces CLI and lib scripts
- Runs any pending migration scripts in order (e.g. new Nginx directives, new packages)
- Updates the version file
Migration scripts live in lib/migrations/ and are named by version (e.g.
4.1.0.sh). When updating from v4.0.0 to v4.2.0, Cipi automatically runs
4.1.0.sh and 4.2.0.sh in order. Your apps, databases, and configurations
are never touched.
For example, the 4.5.5 migration retro-fits existing apps with the new
ll='ls -al' shell alias: it appends the alias to each app's ~/.bashrc
once (only when missing, preserving ownership), so apps created before 4.5.5 get it on the next
cipi self-update.
Automatic maintenance crons
Cipi schedules several root-level jobs during installation. App-level crontabs (scheduler, deploy trigger) are separate — see User crontab.
| Schedule | Job |
|---|---|
| Daily 02:00 | cipi backup run — S3 backups for all apps |
| Daily 03:00 | cipi backup prune --weeks=4 |
| Daily 03:50 | cipi self-update (wrapped by cipi-cron-notify) |
| Sun 04:10 | cipi ssl renew |
| Daily 04:15 | Panel API maintenance (cipi-api-maintain — prune jobs/metrics) |
| Daily 04:30 | cipi api update — soft-update panel Laravel + cipi/api |
Wildcard domains
Cipi does not support wildcard domains (*.myapp.com) natively. The
block is twofold and architectural — not a configuration detail.
Why wildcards are not supported
1 — Domain validation rejects *
Every domain passed to cipi alias add (and cipi app create) is validated
against a strict regex that requires the string to start with [a-zA-Z0-9]. The asterisk
fails immediately, before nginx or certbot are ever touched.
2 — Certbot uses HTTP-01 challenge, which cannot issue wildcard certs
cipi ssl install calls certbot --nginx, which relies on the HTTP-01 (or
TLS-ALPN-01) challenge — placing a verification file on disk and serving it over port 80. Let's
Encrypt only issues wildcard certificates via the DNS-01 challenge, which requires
programmatic access to your DNS provider's API. Cipi does not integrate with any DNS provider, so
even if the validation were bypassed, certbot would refuse to issue the wildcard cert.
Recommended alternative — Multi-SAN certificate
If your subdomains are fixed and enumerable (e.g. api, admin,
www, staging), the correct approach is to add each one as an explicit
alias and let Cipi issue a single SAN certificate covering all of them:
$ cipi alias add myapp api.myapp.com $ cipi alias add myapp admin.myapp.com $ cipi alias add myapp www.myapp.com $ cipi ssl install myapp # single cert, SAN covers all domains
Certbot's --expand flag (used internally by Cipi) adds the new SANs to the existing
certificate without issuing a new one. The SAN list has no meaningful limit for typical use.
Manual wildcard certificate (outside Cipi)
If you need dynamic subdomains (e.g. <tenant>.saas.com), you can obtain a wildcard
certificate manually using a DNS plugin for certbot and place it on the server. Cipi will not
manage, renew, or track it — you own the lifecycle entirely.
# example with the Cloudflare DNS plugin $ pip install certbot-dns-cloudflare $ certbot certonly --dns-cloudflare \ --dns-cloudflare-credentials /root/.cloudflare.ini \ -d "*.myapp.com" -d "myapp.com"
After obtaining the certificate, edit the nginx vhost for the app directly
(/etc/nginx/sites-available/myapp) to reference the wildcard cert paths and add
server_name *.myapp.com myapp.com;. Then reload nginx:
$ nginx -t && systemctl reload nginx
cipi ssl install myapp after manual wildcard setup will overwrite your
custom nginx SSL directives with a Let's Encrypt HTTP-01 certificate. If you manage a wildcard
cert manually, avoid running cipi ssl install on that app.Edit Nginx config
To customise the Nginx vhost for an app, edit the site config directly. After changes, test and reload Nginx.
$ sudo nano /etc/nginx/sites-available/<app> $ sudo nginx -t && sudo systemctl reload nginx
Uninstall Cipi
Cipi does not provide a built-in uninstall command. If you need to completely remove Cipi from a server, follow the steps below in order. This procedure removes every component that Cipi installs — users, services, packages, configs, and data.
1 — Stop and remove all apps
For each app managed by Cipi, remove its system user, home directory, database, nginx vhost, PHP-FPM pool, and supervisor config.
# List all app users (members of cipi-apps group) $ grep cipi-apps /etc/group # For EACH app user, remove everything $ supervisorctl stop <app_user>:* $ rm -f /etc/supervisor/conf.d/<app_user>.conf $ rm -f /etc/nginx/sites-enabled/<app_user> $ rm -f /etc/nginx/sites-available/<app_user> $ rm -f /etc/php/*/fpm/pool.d/<app_user>.conf $ rm -f /etc/sudoers.d/cipi-<app_user> $ mysql -e "DROP DATABASE IF EXISTS <app_user>; DROP USER IF EXISTS '<app_user>'@'localhost'; DROP USER IF EXISTS '<app_user>'@'127.0.0.1';" $ userdel -r <app_user>
2 — Remove the Cipi user and groups
$ userdel -r cipi $ groupdel cipi-ssh 2>/dev/null $ groupdel cipi-apps 2>/dev/null
3 — Remove Cipi binaries, libraries, and data
$ rm -f /usr/local/bin/cipi $ rm -f /usr/local/bin/cipi-worker $ rm -f /usr/local/bin/cipi-cron-notify $ rm -f /usr/local/bin/cipi-auth-notify $ rm -rf /opt/cipi $ rm -rf /etc/cipi $ rm -rf /var/log/cipi
4 — Remove Cipi API (if installed)
$ systemctl stop cipi-queue 2>/dev/null $ systemctl disable cipi-queue 2>/dev/null $ rm -f /etc/systemd/system/cipi-queue.service $ systemctl daemon-reload
5 — Remove Cipi cron jobs
# Edit root crontab and remove all Cipi entries $ crontab -e # Remove lines referencing: cipi self-update, certbot renewal, cache cleanup, RAM drop
6 — Remove Cipi configuration files
# Sudoers $ rm -f /etc/sudoers.d/cipi-sudo $ rm -f /etc/sudoers.d/cipi-api # Logrotate $ rm -f /etc/logrotate.d/cipi-app-logs $ rm -f /etc/logrotate.d/cipi-http-logs $ rm -f /etc/logrotate.d/cipi-security-logs # Unattended upgrades $ rm -f /etc/apt/apt.conf.d/50cipi-unattended-upgrades $ rm -f /etc/apt/apt.conf.d/20cipi-auto-upgrades # System profile and MOTD $ rm -f /etc/profile.d/cipi-env.sh $ echo "" > /etc/motd # MariaDB custom config $ rm -f /etc/mysql/mariadb.conf.d/99-cipi.cnf # PHP custom config (all versions) $ rm -f /etc/php/*/fpm/conf.d/99-cipi.ini # Nginx default page $ rm -f /etc/nginx/sites-available/default $ rm -f /etc/nginx/sites-enabled/default
7 — Purge installed packages
Remove all packages that Cipi installed. Skip any package you want to keep for other purposes.
$ systemctl stop nginx mariadb valkey-server fail2ban supervisor $ systemctl stop php*-fpm $ apt purge -y nginx* mariadb-server mariadb-client valkey-server \ fail2ban supervisor certbot python3-certbot-nginx \ php8.4* php8.5* nodejs $ apt autoremove -y $ apt autoclean
8 — Remove APT repositories
$ add-apt-repository --remove ppa:ondrej/php -y $ rm -f /etc/apt/sources.list.d/mariadb.list $ rm -f /etc/apt/sources.list.d/nodesource.list $ rm -f /etc/apt/keyrings/mariadb-keyring.pgp $ apt update
9 — Remove Composer and Deployer
$ rm -f /usr/local/bin/composer $ rm -f /usr/local/bin/dep
10 — Remove swap file
$ swapoff /var/swap.1 $ rm -f /var/swap.1 # Remove the swap entry from /etc/fstab $ sed -i '/swap\.1/d' /etc/fstab
11 — Restore SSH and PAM defaults
Cipi hardens SSH (disables root login and password auth) and adds PAM hooks. If you need to restore defaults:
# Restore sshd_config to allow password auth (if needed) $ sed -i 's/^PasswordAuthentication no/PasswordAuthentication yes/' /etc/ssh/sshd_config $ sed -i 's/^PermitRootLogin no/PermitRootLogin yes/' /etc/ssh/sshd_config # Remove Cipi PAM hooks $ sed -i '/cipi-auth-notify/d' /etc/pam.d/sshd $ sed -i '/cipi-auth-notify/d' /etc/pam.d/sudo # Restore sysctl $ sed -i '/vm.swappiness/d' /etc/sysctl.conf $ sysctl -p $ systemctl restart sshd
12 — Reset firewall
$ ufw disable $ ufw reset