# ZoPanel > ZoPanel is a fast, secure hosting control panel for Ubuntu and Debian: websites, WordPress, email, DNS, databases, backups and resellers. Free for 10 websites. Pricing: Free for 10 websites and 10 databases; paid plans unlimited (see https://zopanel.net/pricing). Supported systems: Ubuntu 22.04/24.04, Debian 12/13 (x86-64, ARM64). Install: `curl -fsSL https://get.zopanel.net | sudo bash`. ## Getting started ### System requirements Source: https://zopanel.net/docs/requirements ZoPanel installs on a **fresh** Linux server: one without another control panel or web server already configured. This page lists what to prepare before you run the installer, so the installation goes through in one pass. ## Operating systems | System | Versions | Notes | | --- | --- | --- | | Ubuntu | 22.04 LTS, 24.04 LTS | On 22.04 the installer adds Ubuntu's 6.8 HWE kernel | | Debian | 12, 13 | On 12 the 6.12 backports kernel is optional | Both **x86-64 (amd64)** and **ARM64 (aarch64)** are supported. The installer stops on any other system or architecture. A kernel of Linux 6.8 or newer lets ZoPanel compress customers' idle memory in RAM without ever writing it to disk. Ubuntu 24.04 and Debian 13 already have one; see [Install ZoPanel](/docs/install) for how the installer handles Ubuntu 22.04 and Debian 12, and [PHP performance](/docs/php-performance) for what the feature does. ## Memory and CPU | Use | RAM | | --- | --- | | Trying ZoPanel | 1 GB | | Small production server | 2 GB or more | The installer warns when the server has less than about 1 GB, but continues. ZoPanel sizes itself to the machine: - MariaDB's buffer pool is set to 20% of RAM. - On servers with less than 4 GB of RAM and no swap, the installer creates a swap file of 1 to 4 GB. - The memory customers' websites and apps may use together is planned from the server's RAM and re-planned every 10 minutes. ### Measured capacity These figures were measured on test servers. Your results depend on the sites you host. | Server | WordPress sites without page cache | WordPress sites with page cache | | --- | --- | --- | | Ubuntu 24.04, 4 GB RAM, 4 vCPU | 100 (plus 30 apps) | 250 (plus 30 apps) | | Debian 12, 3 GB RAM | 50 (plus 30 apps) | 200 (plus 30 apps) | After a heavy overload, these servers went back to normal within 2 to 5 minutes once the load stopped. ## Disk The installer does not enforce a minimum disk size. Plan for: - the system, ZoPanel, PHP, MariaDB and the optional components you install; - a swap file of 1 to 4 GB on servers under 4 GB of RAM; - your customers' websites, databases and mail; - local backups, which are stored on the same server unless you also send them to S3 storage. The installer turns on disk quotas so each package's disk and file (inode) limits are enforced by the kernel. On most filesystems this works at once; on XFS it takes effect after the next restart. If quotas cannot be enabled, usage is still measured but not enforced. ## A fresh server Use a newly installed server. The installer refuses to continue when it finds: - an Apache web server it did not install itself; - cPanel, DirectAdmin or aaPanel. If you pass `--force`, the installer continues anyway, at your own risk. Inside a container, the host's kernel is used, so the installer skips the kernel upgrade. ## Root access Run the installer as **root** or with `sudo`. ZoPanel itself runs as an unprivileged user (`zopanel`), with a separate privileged agent for system changes. ## Network and ports The installer enables the UFW firewall and opens: | Port | Used for | | --- | --- | | Your SSH port (detected, usually 22) | SSH and SFTP | | 80/tcp | HTTP and Let's Encrypt validation | | 443/tcp | HTTPS websites | | 8888/tcp | The ZoPanel web interface | If your provider also has a cloud firewall or security group, open the same ports there. Optional components open their own ports in UFW when you install them: | Component | Ports | | --- | --- | | Mail server | 25, 465, 587, 143, 993, 110, 995 | | Webmail | 2096 | | Calendars & contacts (CalDAV/CardDAV) | 2080 | | DNS server | 53 (TCP and UDP) | | FTP server | 21 and passive ports 30000-30100 | | Adminer | 8889 | The server also needs outbound HTTPS to download ZoPanel, system packages and PHP. If you plan to run mail, check that your provider allows outbound port 25. ## Hostname and DNS ZoPanel works on the server's IP address straight after installation (`https://YOUR-IP:8888`, with a self-signed certificate). For production, prepare: - **A hostname** for the server, for example `srv1.example.com`. Pass it with `--hostname`. - **A panel domain**, for example `panel.example.com`, with an A record pointing to the server. You set it later in **Settings** to get a trusted Let's Encrypt certificate for the panel. - **A mail hostname** such as `mail.example.com`, if you will host email. Set its reverse DNS (PTR) at your VPS provider for good deliverability. - **Nameservers** such as `ns1.example.com` and `ns2.example.com`, with glue records at your registrar, if you will host DNS zones. ## Checklist 1. Fresh Ubuntu 22.04/24.04 or Debian 12/13, amd64 or arm64. 2. At least 1 GB of RAM (2 GB or more for production). 3. Root or sudo access over SSH. 4. Ports for SSH, 80, 443 and 8888 open in any provider firewall. 5. A hostname and, ideally, a panel domain pointing to the server. Next: [Install ZoPanel](/docs/install). ### Install ZoPanel Source: https://zopanel.net/docs/install ZoPanel installs with a single command on a fresh Ubuntu or Debian server. The installer brings the system up to date, sets up the web stack and starts the panel. Check the [system requirements](/docs/requirements) first. ## Quick install Connect to the server over SSH as root (or a sudo user) and run: ```bash curl -fsSL https://get.zopanel.net | sudo bash ``` To pass options, put them after `sudo bash -s --`: ```bash curl -fsSL https://get.zopanel.net | sudo bash -s -- --php 8.3,8.2 --admin-email you@example.com ``` The bootstrap script checks the OS and architecture, downloads the release manifest, verifies its Ed25519 signature and the binary's SHA-256 checksum, installs `/usr/local/zopanel/bin/zopanel` and then runs `zopanel setup` with your options. ## What the installer does The installer prints each step as it runs: 1. **Checking system**: supported OS and architecture, RAM, and no other web server or control panel. 2. **Updating the system**: `apt update` and a full upgrade of all packages, retried up to 3 times. It then turns on **automatic security updates** (unattended-upgrades) and handles the kernel (see below). 3. **Installing packages**: nginx, MariaDB, UFW, Fail2ban, quota tools, OpenSSH and the PHP repository (ondrej PPA on Ubuntu, packages.sury.org on Debian). 4. **Installing PHP**: the versions from `--php`, plus wp-cli. 5. **Installing language runtimes**: Node.js 22, Composer and Python by default. 6. **Creating users and directories**: the `zopanel` system user and the hosting groups. 7. **Configuring nginx**, **MariaDB** (buffer pool at 20% of RAM, swap on small servers) and **SFTP** (chrooted). 8. **Configuring firewall and fail2ban**: UFW with your SSH port, 80, 443 and 8888 open; Fail2ban for SSH and the panel login. 9. **Enabling disk quotas** and **hiding processes between accounts**. 10. **Installing ZoPanel**: configuration, a self-signed certificate, the first administrator, a `Default` package and the `zopanel` and `zopanel-agent` services. Optional components and a license key are then queued for the panel, which installs or activates them in the background after it starts. The installer is safe to run again: it repairs the configuration without touching your data, and keeps existing administrator accounts. ### Kernel handling ZoPanel can compress customers' idle memory in RAM on Linux 6.8 or newer. If the running kernel is older: | System | What happens | | --- | --- | | Ubuntu 22.04 | Ubuntu's HWE kernel (`linux-generic-hwe-22.04`) is installed by default | | Debian 12 | The installer asks whether to install the 6.12 backports kernel (default: no). It is outside Debian's security support. | | Ubuntu 24.04, Debian 13 | Already on a 6.8+ kernel: nothing to do | | Containers | The host's kernel is used: nothing to do | You can change your mind later: the **Tuning** page shows the command to install a newer kernel. ### The restart question When a new kernel or system libraries need a restart, the installer asks **once, near the start**: ```text A restart is needed to finish (new kernel or system libraries). Restart automatically when the installation is done? [Y/n] ``` If you answer yes, the server restarts 15 seconds after the installer finishes and ZoPanel starts by itself. Queued components are installed after the restart. If you answer no, restart later with `reboot` to finish the update. ## Installer options | Flag | Default | What it does | | --- | --- | --- | | `--profile web\|full` | `web` | `web`: the web stack only. `full`: also mail, webmail, calendars & contacts, DNS, WAF, Apache (.htaccess), FTP, PostgreSQL, Adminer and Docker. | | `--with LIST` | none | Extra components, comma separated: `mail`, `webmail`, `dav`, `dns`, `waf`, `apache`, `ftp`, `postgres`, `mongo`, `adminer`, `docker`, `storage`. | | `--php VERSIONS` | `8.3` | PHP versions to install, comma separated (7.4 to 8.4). The first becomes the default. | | `--admin-user NAME` | `admin` | Login name of the first administrator. | | `--admin-email EMAIL` | none | Administrator email, used for Let's Encrypt and notifications. | | `--hostname HOST` | system hostname | Server hostname. | | `--mail-hostname HOST` | none | Mail server hostname, needed for `mail` and `webmail`. If omitted and `--hostname` has at least three labels (like `srv1.example.com`), that hostname is used. | | `--nameservers LIST` | none | Nameservers for the DNS server, needed for `dns`. | | `--license KEY` | none | License key to activate once the panel runs. | | `--runtimes LIST` | `node22,composer,python` | Language runtimes to install (`node`, `go`, `python`, `composer`). | | `--yes` | off | Ask nothing; defaults apply. No restart unless `--reboot` is also given. | | `--reboot` | off | Restart automatically at the end when a restart is needed. | | `--no-reboot` | off | Never restart; just say when a restart is needed. | | `--keep-kernel` | off | Never install a newer kernel. | | `--kernel-backports` | off | Debian 12: install the 6.12 backports kernel without asking. | | `--no-os-upgrade` | off | Leave the system packages as they are (also skips automatic security updates and the kernel). | | `--force` | off | Continue on an unsupported system or one with another web server. | Notes: - `--reboot` and `--no-reboot` cannot be used together. - If `mail`/`webmail` is chosen without a mail hostname, or `dns` without nameservers, that component is skipped with a message; install it later from **Components**. - To keep secrets out of the process list, pass the license as `ZOPANEL_LICENSE_KEY` and a chosen admin password as `ZOPANEL_ADMIN_PASSWORD` instead of flags. Without a password, a random one is generated. - Without a terminal, questions take their default answer. ### Examples A full server with mail and DNS: ```bash curl -fsSL https://get.zopanel.net | sudo bash -s -- \ --profile full \ --hostname srv1.example.com \ --admin-email admin@example.com \ --mail-hostname mail.example.com \ --nameservers ns1.example.com,ns2.example.com ``` Two PHP versions and a license: ```bash curl -fsSL https://get.zopanel.net | sudo bash -s -- --php 8.3,8.2 --license ZP-XXXX-XXXX-XXXX ``` ## Unattended install (cloud-init) Use `--yes` so nothing is asked, and decide the restart with `--reboot` or `--no-reboot`: ```yaml #cloud-config runcmd: - curl -fsSL https://get.zopanel.net | ZOPANEL_LICENSE_KEY=ZP-XXXX-XXXX-XXXX bash -s -- --yes --reboot --admin-email admin@example.com ``` cloud-init already runs as root, so `sudo` is not needed. The generated admin password is printed in the installer output, which cloud-init writes to its log (usually `/var/log/cloud-init-output.log`). Reset it at any time with `zopanel ctl reset-password admin`. ## When it finishes The installer prints the address and the first login: ```text ZoPanel is ready! URL: https://203.0.113.10:8888 Username: admin Password: •••••••••••••••• Save this password now — it is not stored anywhere in plain text. The certificate is self-signed until you set a panel domain in Settings. ``` Open the URL in your browser, accept the self-signed certificate warning once, and sign in. Then follow the [Quick start](/docs/quick-start). ### If something goes wrong - Run `zopanel ctl doctor` to check the server and see how to fix what is wrong. - Lost the password: `zopanel ctl reset-password admin`. - Locked out by the panel IP restriction: `zopanel ctl allow-ip --clear`. - A failed step can be retried by running the installer again. ### Quick start Source: https://zopanel.net/docs/quick-start This guide takes you from a freshly installed server to a first hosted website. It assumes the installer has finished and printed the panel URL and admin password (see [Install ZoPanel](/docs/install)). ## 1. Sign in 1. Open `https://YOUR-SERVER-IP:8888` in your browser. 2. The panel uses a self-signed certificate until you set a panel domain, so the browser shows a warning. Continue to the site. 3. Sign in with the username (`admin` unless you chose another) and the password printed by the installer. If you lost the password, reset it on the server: ```bash zopanel ctl reset-password admin ``` ## 2. Secure the administrator login Open **My account** from the user menu at the top right. ### Change the password Under **Change password**, enter the current password and a new one of at least 8 characters, then click **Update password**. ### Choose your own login name Bots try `admin` first. Under **Login name**, enter a hard-to-guess name and click **Change login name**. Use the new name the next time you sign in. ### Turn on two-factor authentication 1. Under **Two-factor authentication**, click **Enable 2FA**. 2. Scan the QR code with Google Authenticator, Authy, 1Password or a similar app. 3. Enter the 6-digit code and click **Verify & enable**. 4. Save the **recovery codes** somewhere safe. Each one signs you in once if you lose your phone, and they are shown only now. If you ever lose both your phone and the recovery codes, an administrator with root access can run `zopanel ctl disable-2fa USER`. To make 2FA mandatory for others, go to **Settings → General → Require two-factor authentication** and choose **Administrators and resellers** or **Everyone**. ## 3. Give the panel a domain and SSL 1. Create an A record for a domain such as `panel.example.com` pointing to the server's IP. 2. Go to **Settings → General**. 3. Fill in **Administrator email** (used for Let's Encrypt registration and notifications) and click **Save**. The certificate cannot be requested without it. 4. In **Panel domain & SSL**, enter the domain in **Panel domain**. 5. Click **Issue certificate**. A task requests a Let's Encrypt certificate. 6. Open the panel at `https://panel.example.com:8888`. The browser warning is gone, and the certificate renews automatically. You can also limit who can open the panel in **Restrict panel access**; see [Panel settings](/docs/panel-settings). ## 4. Create a package A package is a resource plan that you assign to hosting accounts. The installer created one named `Default` (10 websites, 10 databases, 10 GB disk, 1 GB RAM, one CPU core). To create your own: 1. Go to **Packages** and click **New package**. 2. Enter a **Package name**, for example `Starter`. 3. Set the limits: websites, databases, **Disk (MB)**, **CPU (%)** (100 = one full core), **RAM (MB)**, **PHP memory_limit (MB)**, **PHP workers** and the others. For most limits `0` means unlimited. 4. Turn **SFTP** on so the customer can upload files. 5. Click **Save**. See [Packages and limits](/docs/packages-limits) for every field. ## 5. Create a hosting account Each account gets its own Linux user, home directory, PHP and resource limits. 1. Go to **Accounts** and click **New account**. 2. Enter a **Username** (3-16 lowercase letters and digits; it cannot be changed later), an **Email** and a **Password**. The password is used for both the panel and SFTP. 3. Keep the **Role** as **Customer**. (**Reseller** needs a Pro license.) 4. Choose the **Package** you created. 5. Click **Create**. The panel shows the details to send to your customer, including the **Panel URL**. The Free plan allows up to 3 hosting accounts, 10 websites and 10 databases on the server. See [Licensing](/docs/licensing). ## 6. Add a website 1. Point the domain's A record to the server's IP (do this first so SSL can be issued straight away). 2. Go to **Websites** and click **New website**. 3. Choose the **Owner**: the account you just created. 4. Enter the **Domain** without `http://`, for example `example.com`. 5. Choose the **Website type**: WordPress, PHP, Laravel, HTML, Node / Proxy or Git deploy. 6. Pick a **PHP version** and keep **Free SSL (Let's Encrypt)** on. 7. Click **Create website**. For WordPress, ZoPanel creates the database and installs WordPress for you. The full walkthrough, including DNS and file uploads, is in [Your first website](/docs/first-website). ## What to do next - Install the optional components you need (mail, DNS, WAF and more) from **Components**: see [Optional components](/docs/components). - Set up alerts by Telegram, email or webhook in **Settings → Alerts**. - Review the **Security Center** on the **Security** page and the suggestions on the **Tuning** page; many come with a one-click fix. - Turn on **Automatic updates** in **Settings → Updates**: see [Updating ZoPanel](/docs/updating). ### Your first website Source: https://zopanel.net/docs/first-website This page walks through hosting one website from start to finish. You need a hosting account to own the website; if you have not created one yet, see the [Quick start](/docs/quick-start). ## 1. Point the domain to the server Do this first: Let's Encrypt can only issue a certificate once the domain resolves to your server. At your DNS provider, create these records (replace the IP with your server's): | Type | Name | Value | | --- | --- | --- | | A | `example.com` | `203.0.113.10` | | A | `www` | `203.0.113.10` | Add AAAA records too if the server has IPv6. DNS changes can take from a few minutes to a few hours to reach everyone. You can check from your computer: ```bash dig +short example.com ``` If you host DNS on ZoPanel itself (the DNS server component), create the zone under **DNS** and set your nameservers at the registrar instead. ## 2. Create the website 1. Go to **Websites** and click **New website**. 2. **Owner**: choose the hosting account. (Customers signed in to their own account skip this.) 3. **Domain**: enter the domain without `http://`, for example `example.com`. 4. Keep **Also serve www.example.com** checked if you want both addresses to work. 5. **Website type**: | Type | Use it for | | --- | --- | | WordPress | Installs WordPress automatically | | PHP | Any PHP application | | Laravel | PHP with the document root at `/public` | | HTML | Static HTML files | | Node / Proxy | A Node.js, Python or Go app listening on `127.0.0.1` at a port you choose | | Git deploy | Next.js, Node, Python, Go, Laravel and more, built from a repository | 6. **PHP version**: choose one of the installed versions. 7. Keep **Free SSL (Let's Encrypt)** on. 8. Click **Create website**. The website's files live in the account's home directory: ```text /home/USERNAME/domains/example.com/public_html ``` Until you upload your own files, visitors see a placeholder page. ## 3. Get the SSL certificate With **Free SSL (Let's Encrypt)** on and DNS already pointing to the server, the certificate is issued when the website is created. If DNS was not ready yet, you do not need to do anything: ZoPanel checks every hour and requests the certificate as soon as the domain resolves to the server. To issue it right away: 1. Open the website and go to the **SSL** tab. 2. Click **Issue certificate**. 3. Turn on **Redirect HTTP to HTTPS** so every visitor uses the secure address. Certificates renew automatically 30 days before they expire. If you have a certificate from another provider, paste it under **Custom certificate** in the same tab. ## 4. Upload your files ### With the File Manager 1. Go to **File Manager** (or open it from the website). 2. Open `domains/example.com/public_html`. 3. Drag and drop files onto the page, or click **Upload**. 4. To upload a whole site quickly, upload a `.zip` or `.tar.gz` archive, then select it and click **Extract**. The File Manager also edits files, changes permissions (**Permissions (chmod)**), compresses and renames. ### With SFTP SFTP is available when the account's package has **SFTP** turned on. Use any SFTP client (FileZilla, WinSCP, Cyberduck or `sftp`): | Setting | Value | | --- | --- | | Protocol | SFTP | | Host | Your server's IP or hostname | | Port | The server's SSH port (usually 22) | | Username | The hosting account's username | | Password | The account's password, as set when the account was created | The connection is restricted (chrooted) to the account's home directory; upload into `domains/example.com/public_html`. The **FTP / SFTP** page shows the connection details. Changing the panel password under **My account** does not change the SFTP password. To sign in with a key instead of a password, add your public key under **My account → SFTP & SSH keys** (signed in as the account). For classic FTP, install the FTP server component and create FTP accounts restricted to one folder. ## 5. Install WordPress ### When you create the website Choose **WordPress** as the website type and fill in **WordPress settings**: **Site title**, **Admin email**, **Admin username** and **Admin password**. ZoPanel creates a database and installs WordPress with wp-cli. The progress is shown as a task. ### On an existing website 1. Open the website's **Overview** tab. 2. Under **Applications**, click **Install WordPress**. 3. Enter the details and click **Install**. A database is created and WordPress is installed with wp-cli. ### After installation The website's **WordPress** tab lets you sign in to wp-admin without a password (**Log in to WordPress**), update core, plugins and themes, apply security hardening, and create a staging copy. For speed, turn on the **Page cache** in the **PHP & config** tab. Logged-in users, admin pages, carts and POST requests always bypass it. On our test servers, page cache raised capacity from 100 to 250 WordPress sites on a 4 GB VPS. ## 6. Check that it works - Visit `https://example.com`: the padlock should show a valid certificate. - The **Logs** tab shows the access and error logs if something fails. - The **Analytics** tab shows visitors and requests, computed from the web server logs. ## Troubleshooting | Problem | What to check | | --- | --- | | Certificate not issued | The domain's A record must point to this server, and port 80 must be open. Wait for DNS, then click **Issue certificate**. | | Placeholder page still shown | Files must be inside `public_html`, with an `index.php` or `index.html`. | | Creating the website is refused | The account's package or the server's license limits the number of websites; the error message says which. See [Packages and limits](/docs/packages-limits) and [Licensing](/docs/licensing). | ### Licensing Source: https://zopanel.net/docs/licensing ZoPanel runs on the Free plan out of the box, with no key and no time limit. A paid license raises the limits and unlocks extra features. This page explains what each plan includes and how to activate, move and renew a license. ## Free and paid plans | | Free | Paid plans | | --- | --- | --- | | Hosting accounts | 3 | Set by your plan | | Websites on the server | 10 | Set by your plan | | Databases on the server | 10 | Set by your plan | | Reseller accounts | No | Pro | | Remote backups to S3 storage | No | Pro | | API tokens | No | Pro | | S3 object storage component | No | Pro | | White-label (your own panel name) | No | Business | The limits count everything on the server, across all accounts. Everything else in the panel, such as websites, SSL, the File Manager, backups on the server's disk, WordPress tools, the firewall and the other components, works on Free. To see what your server has, go to **Settings → License**. The card shows the plan, the license key, the expiry date, the number of accounts and websites in use against the limits, and a badge for each feature: **Resellers**, **Remote backup**, **Multi PHP**, **API** and **White-label**. It also shows the **Server ID**, which identifies this machine. ## Buy a license In **Settings → License**, the **Buy a license** card lists the available plans. Enter your email and pay by card, Apple Pay or Google Pay through OnePay, or with PayPal. After payment, the license is activated on this server automatically. You can also buy on the website and receive the key by email. Your licenses are listed in your customer account at [https://zopanel.net/account](https://zopanel.net/account). ## Activate a license key ### In the panel 1. Sign in as an administrator. 2. Go to **Settings → License**. 3. In **Activate license**, enter the key (it looks like `ZP-XXXX-XXXX-XXXX`). 4. Click **Activate license**. The panel contacts the license server, which binds the license to this server and returns a signed token. The new plan applies at once. ### During installation Pass the key to the installer: ```bash curl -fsSL https://get.zopanel.net | sudo bash -s -- --license ZP-XXXX-XXXX-XXXX ``` To keep the key out of the process list, use the environment variable instead: ```bash curl -fsSL https://get.zopanel.net | sudo ZOPANEL_LICENSE_KEY=ZP-XXXX-XXXX-XXXX bash ``` The panel activates the key within a minute of starting. If the license server cannot be reached yet, it keeps retrying. If the key is refused, the panel sends an alert and you can enter a valid key under **Settings → License**. ### Offline activation For a server without internet access, use **Offline activation** in **Settings → License**: 1. Note the **Server ID** shown on the License card. 2. Contact ZoPanel support with your license key and the Server ID to get a signed license token for that server. 3. Paste the token (it starts with `ZPL1.`) into **Offline activation** and click **Install token**. An offline token is checked on the server itself and is only valid for the Server ID it was issued for. ## How the license stays valid An online license is bound to one server and verified with the license server regularly. If the server cannot reach the license server, the panel keeps the license during a **14-day offline grace period** and shows an **Offline grace** badge. After that, it falls back to the Free plan until verification succeeds again. Keep the server clock correct (NTP). A clock set back by more than a day makes the license invalid until it is fixed. ## Move a license to another server A license works on one server at a time. To move it, for example when you replace a VPS: 1. Sign in to your customer account at [https://zopanel.net/account](https://zopanel.net/account). 2. Open the license. 3. Click **Move to another server** and confirm. This releases the license from its current server. 4. On the new server, activate the key in **Settings → License**, or install with `--license`. You can move a license **3 times per year**. The page shows how many moves you have left. The old server does not need to be reachable. If you have used all moves this year, open a support ticket. The old server stops refreshing the license and drops to Free after its grace period. To remove a license from a server yourself, use **Remove license** in **Settings → License**. ## When a yearly license expires When a license with an end date expires and is not renewed: - The panel falls back to the **Free plan** limits: 3 accounts, 10 websites and 10 databases. - **Nothing is deleted.** Existing accounts, websites, databases, mail and files keep running. - New accounts, websites or databases cannot be created while the server is over the Free limits. - Paid features stop: reseller accounts can no longer manage customers, remote backups to S3 stop, API tokens no longer work, and the white-label panel name is not used. Renew or activate a new key, and the full plan comes back immediately. ## Check from the command line On the server, `zopanel ctl info` shows the version, Server ID, plan and whether it is valid, and the number of websites and accounts: ```bash zopanel ctl info ``` ## Questions **Do I need a license to try ZoPanel?** No. The Free plan has no time limit. **Can I use one license on a test server and a production server?** No. Each server needs its own license, or runs on Free. ### Updating ZoPanel Source: https://zopanel.net/docs/updating ZoPanel updates itself as one signed binary. Each update is guarded: the panel database is saved first, the new version must pass a health check, and if it does not, the previous version and its database come back automatically. This page covers manual and automatic updates, and how to roll back by hand. ## What an update does 1. **Download and verify.** The release manifest for your channel is downloaded and its Ed25519 signature checked. The binary must match the SHA-256 in that signed manifest. Unsigned or modified releases are refused. 2. **Keep the previous version.** The running binary is copied aside so it can be restored. 3. **Snapshot the database.** The panel database is copied consistently to `/var/lib/zopanel/pre-update/`. The last 3 snapshots are kept. If the snapshot fails, the update stops and the services are not restarted on the new version. 4. **Restart on the new version.** Both services, `zopanel-agent` and `zopanel`, restart. The new version migrates the database on start. 5. **Health check.** The guard waits up to 3 minutes for both services to be active, the agent to answer and the panel's health check to pass, then checks again after 20 seconds to make sure it stays up. 6. **Automatic rollback.** If the health check fails, the previous binary and the database snapshot are put back, and the services restart on the old version. The guard runs as its own systemd unit, so a dropped SSH connection or closed browser tab does not stop it halfway. Your websites, databases and mail keep running throughout: only the panel and its agent restart. ## Update from the panel 1. Go to **Settings → Updates**. 2. The card shows the **Installed version** and **Latest version**. Click **Check now** to look again. 3. When a new version is available, its release notes are shown. Click **Update now**. The update runs as a task. The panel is unavailable for a few moments while it restarts. ## Update from the command line As root on the server: ```bash zopanel update ``` It prints each step and ends with `ZoPanel X.Y.Z is up and healthy`, or with the reason it rolled back. If you are already on the latest version, it says so and does nothing. To check the installed version: ```bash zopanel version ``` ## Automatic updates and channels In **Settings → Updates → Update preferences**: | Setting | Options | Effect | | --- | --- | --- | | **Channel** | Stable, Beta | Which releases the server installs | | **Automatic updates** | On / off | Installs new releases at 04:00 server time | Automatic updates are off until you turn them on. When they are off, the panel checks once a day and sends an **Update available** alert once per new release. Automatic updates use the same guard as manual ones. ## Alerts about updates In **Settings → Alerts**, turn on these events to be told about updates by Telegram, email or webhook: - **Panel update failed or rolled back**: the new version did not become healthy and was rolled back, or the update failed. - **Update available**: a new release is out (when automatic updates are off). If the server restarts in the middle of the health check, the panel reports that the update was not checked, and you can decide whether to keep it or roll back. ## Roll back by hand If a new version works but you prefer the previous one, run as root: ```bash zopanel rollback ``` This puts the previous binary back and restarts the services. What happens to the panel database depends on the situation: | Situation | Database | | --- | --- | | The last update failed less than 30 minutes ago | Restored from the snapshot taken before that update | | The update succeeded, or the snapshot is older | **Kept as it is**, so changes made since (accounts, websites, mailboxes) are not lost | The command prints which case applies and how old the snapshot is. ### `--db`: also restore the database ```bash zopanel rollback --db ``` Also restores the database snapshot taken before the last update, whatever its age. Use it when the previous version cannot run on the current database. **Everything changed in the panel since that update is lost**: accounts, websites, databases, mailboxes and settings created or changed after it. Website files and the contents of customer databases are not part of the panel database, but the panel's records of them are. ### `--binary-only`: never touch the database ```bash zopanel rollback --binary-only ``` Puts the previous binary back and always keeps the current database. Rollback fails with `no previous version to roll back to` if no previous binary is saved, for example right after a fresh installation. ## Troubleshooting | Problem | What to do | | --- | --- | | The panel does not open after an update | Wait up to 3 minutes for the guard to finish. Then run `systemctl status zopanel zopanel-agent` and `journalctl -u zopanel`. | | The update rolled back | The alert and the task log give the reason. Run `zopanel ctl doctor`, and send `zopanel ctl support-bundle` to support if needed. | | Websites behave differently after an update | Run `zopanel ctl rebuild` to apply every account and website's configuration again. | ## Updating the operating system ZoPanel updates do not upgrade your system packages. The installer turns on automatic security updates (unattended-upgrades). The **Security Center** on the **Security** page tells you when security updates are pending or a restart is needed after a kernel update. ## Configuration ### Panel settings Source: https://zopanel.net/docs/panel-settings The **Settings** page holds the server-wide configuration of ZoPanel. Only administrators can open it. It is split into tabs: **General**, **Alerts**, **Remote backups**, **License**, **AI assistant**, **Hooks**, **Updates** and **System**. This page covers the settings you are most likely to change; licensing and updates have their own pages. ## General ### Basic settings | Setting | What it does | | --- | --- | | **Administrator email** | Used for Let's Encrypt registration and notifications. Required before you can issue the panel certificate. | | **Default PHP version** | The PHP version preselected for new websites. | | **Panel name (white-label)** | Shown instead of "ZoPanel". Requires a Business license. | | **Automatic backups** | Off, Daily or Weekly (Sunday). Runs at 02:00 server time. | | **Backups to keep per account** | 1 to 90. | | **Require two-factor authentication** | See below. | | **Send the activity log to** | See below. | | **Keep deleted accounts (days)** | See below. | | **Let resellers oversell** | Off: a reseller's customers together stay within the reseller's own plan (disk and websites). On: each customer is only held to its own package. | Click **Save** after changing them. ### Require two-factor authentication Choose who must use 2FA: | Option | Who | | --- | --- | | **Not required** | Nobody is forced (default) | | **Administrators and resellers** | Every administrator and reseller | | **Everyone** | All users, including customers | Users without 2FA are asked to set it up at their next sign-in, and cannot continue until they do. Each user sets it up under **My account → Two-factor authentication** with an authenticator app, and gets recovery codes. If an administrator loses both the phone and the recovery codes, root on the server can turn 2FA off for that user: ```bash zopanel ctl disable-2fa USERNAME ``` ### Send the activity log to (remote syslog) Every entry of the **Activity Log** can also be sent to a syslog server outside this one. Someone who later takes over this server cannot change that copy. Enter the target as `udp://host:port` or `tcp://host:port`: ```text udp://logs.example.com:514 ``` Leave the field empty to turn it off. If the syslog server is slow or unreachable, the panel is not held up; the local activity log and the system journal keep every entry. ### Keep deleted accounts (days) When you delete a hosting account, its websites, databases and mailboxes stay recoverable for this many days (0 to 90, default 7). Set `0` to delete everything at once. ### Panel domain & SSL By default the panel uses a self-signed certificate at `https://SERVER-IP:8888`. To use a trusted certificate: 1. Point a domain such as `panel.example.com` to the server (A record). 2. Make sure **Administrator email** is filled in and saved. 3. Enter the domain in **Panel domain**. 4. Click **Issue certificate**. A task requests a Let's Encrypt certificate. The panel is then available at `https://panel.example.com:8888`, and the certificate renews automatically. The same certificate is also used by the mail server and webmail when they are installed. ### Restrict panel access Limit the panel to your own addresses, such as your office or VPN. 1. In **Restrict panel access**, enter IP addresses or networks in CIDR notation, one per line: ```text 203.0.113.10 198.51.100.0/24 ``` 2. Click **Save**. Leave the list empty to allow everyone. Include the address you are connecting from, or you will lock yourself out. If that happens, run on the server: ```bash zopanel ctl allow-ip --clear ``` You can also replace the list with a single address: `zopanel ctl allow-ip 203.0.113.10`. Both commands restart the panel. If another ZoPanel server manages this one (central management), add that server's IP to the list. ## Alerts The **Alerts** tab sends notifications to administrators. Turn on one or more channels: | Channel | Fields | | --- | --- | | **Telegram** | Bot token (create a bot with @BotFather) and Chat ID (from @userinfobot) | | **Email (SMTP)** | SMTP host, Port, Username, Password, From, To (comma separated) | | **Webhook (Discord / Slack)** | Type (Discord, Slack or JSON) and URL | Then choose the **Events** that trigger an alert, such as: - Website down, Service down or restarted, Fleet server unreachable - SSL expiring soon, SSL renewal failed, Domain expiring soon - High CPU usage, High memory usage, Disk almost full - Failed panel logins, Login from a new IP - Backup failed, Malware detected, Deployment failed - Mail sending limit reached, Mail queue growing, Server IP on a blocklist - Update available, Panel update failed or rolled back Set the thresholds for the resource alerts in **CPU %**, **RAM %** and **Disk %**. Click **Save**, then **Send test** to check the channels. Customers have their own notification settings for their websites, under **My account**. ## Other tabs | Tab | What it holds | | --- | --- | | **Remote backups** | Copy every backup to S3-compatible storage (AWS S3, Cloudflare R2, Backblaze B2, Wasabi, DigitalOcean, MinIO). Requires a Pro license. | | **License** | Plan, limits, activation and offline tokens. See [Licensing](/docs/licensing). | | **AI assistant** | An optional assistant that answers questions about websites and the server using live panel data. It needs your own Anthropic API key, and changes only run after the user confirms. | | **Hooks** | Event webhooks to your own HTTPS URLs (signed with HMAC-SHA256), and root-owned hook scripts that run after each event. | | **Updates** | Version, channel and automatic updates. See [Updating ZoPanel](/docs/updating). | | **System** | Hostname, OS, kernel and component versions; **PHP performance**; **Disk quotas**; **IP addresses** for dedicated IPs per account; and **Central management**. | The **PHP performance** card is described in [PHP performance](/docs/php-performance). In **Disk quotas**, click **Enable quotas** if the installer could not turn them on; without quotas, disk and file limits are only measured, not enforced. ## Settings from the command line A few settings can be fixed from the server when the panel is not reachable: | Command | Effect | | --- | --- | | `zopanel ctl reset-password USER` | Sets a new random password (or `--password PASS`) and signs the user out everywhere | | `zopanel ctl disable-2fa USER` | Turns off 2FA for the user | | `zopanel ctl allow-ip IP` / `--clear` | Replaces or clears the panel IP restriction | | `zopanel ctl info` | Version, Server ID, plan, websites and accounts | | `zopanel ctl doctor` | Checks the server and says how to fix what is wrong | ### Packages and limits Source: https://zopanel.net/docs/packages-limits A **package** is a resource plan you assign to hosting accounts. Every account on a package gets the same limits, enforced by the Linux kernel, so one busy or hacked website cannot take the whole server down. Administrators and resellers manage packages under **Packages**. ## Create or edit a package 1. Go to **Packages**. 2. Click **New package**, or the edit button on an existing one. 3. Enter a **Package name** and set the limits (see the tables below). 4. Click **Save**. When you edit a package, the new limits are applied at once to every account that uses it, including the PHP settings. The installer creates a package named `Default`: 10 websites, 10 databases, 20 cron jobs, 10 GB disk, 100% CPU (one core), 1 GB RAM, 200 processes, PHP `memory_limit` 256 MB, 10 PHP workers and SFTP on. ## Counts | Field | Limits | `0` means | | --- | --- | --- | | **Websites** | Websites the account can create | Unlimited | | **Databases** | Databases | Unlimited | | **Cron Jobs** | Scheduled tasks | Unlimited | | **FTP** | Extra FTP accounts | Unlimited | | **Email** | Mailboxes | Unlimited | | **Docker apps** | Apps the customer may install from the App Store (n8n, Uptime Kuma…). Their memory counts toward the package memory. | None allowed | | **Sub-accounts (resellers)** | Customer accounts a reseller on this package may create. Only administrators see this field. | Unlimited | ## Resources | Field | Unit | How it is enforced | | --- | --- | --- | | **Disk (MB)** | MB | Filesystem quota: the account cannot write past it | | **Files (inodes)** | Files and folders | Filesystem quota. Needs disk quotas (**Settings → System → Disk quotas**) | | **CPU (%)** | % of one core (100 = one full core, 200 = two cores) | systemd cgroups | | **RAM (MB)** | MB, for all the account's websites, apps and PHP together | systemd cgroups | | **Processes** | Number of processes | systemd cgroups | | **Disk speed (MB/s)** | MB/s of disk throughput | systemd cgroups | | **Database connections** | Per database user | MariaDB | | **Emails per hour** | Outgoing recipients per hour | Mail server sending limit | `0` means unlimited for these fields, with three exceptions that have an **automatic** value when left empty: | Field | Empty (automatic) | `-1` | | --- | --- | --- | | **Disk speed (MB/s)** | About 40 MB/s per GB of package RAM (2 GB ≈ 80 MB/s), between 30 and 300 MB/s. Not capped when RAM is unlimited. | Unlimited | | **Database connections** | 3 × PHP workers, at least 30 | Unlimited | | **Emails per hour** | The server default set in **Email → Sending limits** | Unlimited | Without disk quotas, disk and inode usage is only measured, and one account could fill the whole disk. Check that quotas are **enforced** in **Settings → System → Disk quotas**. ## PHP settings | Field | What it does | | --- | --- | | **PHP memory_limit (MB)** | PHP's `memory_limit` for the account's websites | | **PHP workers** | Maximum PHP processes serving requests at the same time for the account | | **PHP always running** | The account's PHP never sleeps when idle (see below) | Each account runs its own isolated PHP-FPM pool per PHP version, inside the account's limits. A website's diagnostics suggest raising **PHP workers** when too many PHP requests arrive at once; the **Tuning** page warns when all packages together allow more PHP processes than the RAM can hold. Website owners can adjust other PHP options for one website in the website's **Tools** tab, under **PHP settings for this website**: `upload_max_filesize`, `post_max_size`, `max_input_vars`, `max_input_time`, `session.gc_maxlifetime`, `date.timezone`, `short_open_tag` and `output_buffering`. The memory limit always comes from the package. ### PHP always running By default, the PHP of a quiet website stops after an idle time and starts again on the next visit (see [PHP performance](/docs/php-performance)). Turn on **PHP always running** for premium plans: the sites' PHP never sleeps, so every visit is as fast as possible. It costs the pool's memory (about 20-60 MB per site) even when nobody visits. The package list shows each package's PHP mode as **Always running** or **Sleeps when idle**. ## Access | Switch | What it allows | | --- | --- | | **SFTP** | Chrooted SFTP access to the account's home directory | | **Terminal** | A sandboxed shell in the browser, limited to the home directory | ## Assign a package Choose the package when you create an account (**Accounts → New account → Package**), or edit the account later to change it. **No package (unlimited)** leaves the account without limits; use it only for your own trusted accounts. Customers see their limits and current usage on the **Resources** page. Administrators can open the same view for any account from **Accounts**. ## How limits apply - **CPU, RAM, processes and disk speed** are cgroup limits on everything the account runs: PHP, apps, cron jobs and shells. A process that goes over the RAM limit is stopped by the kernel inside the account, without touching other accounts. - **Disk and inodes** are kernel quotas. When an account is full, writes fail for that account only. - **Database connections** are set per database user in MariaDB. - **Emails per hour** counts recipients sent through PHP `mail()` and SMTP logins. Messages over the limit are refused, which stops a hacked website from getting the server's IP blocklisted. - **Counts** (websites, databases, mailboxes…) are checked when something is created. The server's license also limits the total number of accounts, websites and databases across all packages: see [Licensing](/docs/licensing). ## Packages for resellers Resellers (Pro license) create their own packages for their customers. These packages are always held within the reseller's own plan: - A reseller cannot save a package that gives more than the reseller's own package allows: any count or resource above the reseller's own (websites, disk, CPU, RAM, PHP memory, PHP workers, apps, disk speed, database connections, emails per hour and so on), or SFTP, terminal access or always-on PHP when the reseller's own package does not include them. The panel refuses the package and lists what is not allowed. - All of a reseller's accounts together stay within the reseller's own websites and disk space, unless the administrator turns on **Let resellers oversell** in **Settings → General**. Then each customer is only held to its own package. - Resellers do not see the **Sub-accounts (resellers)** field. When the administrator edits a reseller's package, the customers' packages of that reseller are held to the new limits too. Resellers cannot use or assign packages that belong to another reseller. ### PHP performance Source: https://zopanel.net/docs/php-performance On a shared hosting server, most websites are quiet most of the time. ZoPanel uses that: PHP of quiet sites goes to sleep, its compiled code stays on disk so it wakes up fast, idle memory is compressed in RAM, and an overloaded server sheds work it can no longer serve. This page explains each mechanism and the settings that control it. On our test servers, this let an Ubuntu 24.04 VPS with 4 GB RAM and 4 vCPU run 100 WordPress sites without page cache, or 250 with page cache, each time plus 30 apps. A Debian 12 server with 3 GB ran 50 and 200. After a heavy overload, they were back to normal 2 to 5 minutes after the load stopped. ## Idle PHP sleeps Each hosting account has its own PHP-FPM pool per PHP version. Even with no visitors, a pool's master process uses about 25 MB, so hundreds of quiet accounts would waste gigabytes. ZoPanel stops a pool once it has had no work for the idle time. The pool's socket stays open, so the next request starts PHP again within a few hundred milliseconds. Visitors do not see an error, only a slightly slower first page. ### Set the idle time 1. Go to **Settings → System**. 2. In the **PHP performance** card, choose **Stop idle PHP after**: 5, 15 or 30 minutes, 1 or 3 hours, or **Never (always running)**. **30 minutes** is recommended and is the default: long enough that a site visited every few minutes is always warm. A shorter time saves memory on crowded servers; **Never** keeps every site's PHP running. The card also shows how many pools are running (and how many are always on), the **Memory used by PHP**, and the size of the **Compiled code cache**. A few details: - Uptime monitoring requests do not count as visits, so monitoring alone does not keep a quiet site awake. - When memory runs short, idle pools are stopped sooner, longest idle first, without waiting for the idle time. Pools that are serving requests are never stopped for this. ## PHP always running For premium plans, turn on **PHP always running** in the package (**Packages → Edit package**). Those accounts' pools are started at boot and never stopped for idleness, so no visitor meets a cold start. Each always-on site keeps its pool's memory (about 20-60 MB) even when nobody visits. See [Packages and limits](/docs/packages-limits). ## OPcache file cache OPcache keeps compiled PHP in memory, which is lost when a pool stops. ZoPanel also keeps each account's compiled scripts on disk, in `/var/cache/zopanel/opcache/USERNAME`. When a sleeping pool starts again, it loads the compiled code from there instead of compiling every script, so the first request is nearly as fast as a warm one. Each account's cache folder is private to that account (mode 0700), so no account can read or plant another's compiled code. Scripts not loaded for 30 days, and the folders of deleted accounts, are cleaned up daily. If the **Tuning** page reports that OPcache is off for a PHP version, install that version's OPcache package as suggested. ## The customer memory plan ZoPanel divides RAM between the system and customers: - The **system** (databases, mail, nginx, the panel and the kernel) keeps a reserve sized to the machine and to what it actually used at its peak over the last day, and never more than half of RAM. - **Customers** (all PHP pools, apps, shells and Docker apps together) may use the rest, as a hard limit. The plan is re-calculated every 10 minutes. The **PHP performance** card shows it: "websites and apps of all customers may use X together; the system keeps Y". Under a burst that wakes hundreds of sites at once, the kernel throttles or ends customer processes, preferably a PHP worker that restarts at once, while SSH, the panel, nginx and the databases keep working. ## Memory compression (zswap) On **Linux 6.8 or newer**, ZoPanel compresses customers' idle memory (sleeping PHP masters, OPcache, idle apps) in RAM with zswap, using zstd (or lz4 as a fallback). Compressed pages are **never written to disk**: what does not fit compressed simply stays in RAM. You get more sites in the same RAM without the disk swapping that makes an overloaded server slow to recover. It turns on automatically when: 1. The kernel is 6.8 or newer: Ubuntu 24.04 and Debian 13 out of the box, Ubuntu 22.04 with the HWE kernel the installer adds, Debian 12 with the backports kernel. 2. The server has a swap device. The installer creates one on servers with less than 4 GB of RAM. If there is none, the **Tuning** page suggests creating a swap file, with a one-click fix. ### Check it on the Tuning page Go to **Tuning** (under **Server** in the menu): - **Memory compression is on** shows the current compression ratio. - **Kernel X cannot compress customers' memory safely** means the kernel is older than 6.8. The suggestion includes the command to install a newer kernel. On Ubuntu 22.04: ```bash apt install --install-recommends linux-generic-hwe-22.04 && reboot ``` On Debian 12 (backports kernel, outside Debian's security support): ```bash echo 'deb http://deb.debian.org/debian bookworm-backports main' > /etc/apt/sources.list.d/backports.list && apt update && apt install -t bookworm-backports linux-image-amd64 && reboot ``` On ARM64 servers, the Debian package is `linux-image-arm64`. Restart the server at a quiet time: websites are offline during the reboot. ## Load shedding When a server is badly overloaded, requests pile up in the PHP sockets faster than PHP can serve them. Most of those visitors have already given up, yet PHP would still run every request, keeping hundreds of pools awake and the server short of memory long after the load is gone. ZoPanel sheds that work, but only when all of these are true: - customers' memory has been badly short for at least a minute (close to the customer limit, or processes waiting for memory 70% of the time); - a pool's queue has been full for at least 30 seconds. The waiting requests of that pool are dropped, at most once per pool per minute. Visitors still waiting get a "This website is very busy right now" page that reloads by itself. The pools can then go idle and free their memory. A server running normally never reaches this point. ## Tips - Turn on the **Page cache** (website → **PHP & config** tab) for WordPress sites: it is the single biggest gain. The **Tuning** page lists WordPress sites without it and can turn it on for them. - Keep the idle time at 30 minutes unless the server is short of memory. - Use **PHP always running** only for the plans that need it. - Watch the **Tuning** page for PHP-FPM that could exhaust the RAM, missing swap and old kernels. ### Optional components Source: https://zopanel.net/docs/components A default ZoPanel install (`--profile web`) contains only the web stack: nginx, PHP, MariaDB, SFTP, the firewall and the panel. Everything else is an optional **component** that you add when you need it, because every component costs memory. You can choose components when you install, or add them at any time from the panel. ## Available components | ID | Component | What it adds | Typical extra RAM | | --- | --- | --- | --- | | `mail` | Mail server | Postfix, Dovecot and Rspamd: mailboxes, forwarders, DKIM, spam filter, Sieve rules | ~300 MB | | `webmail` | Webmail | Read and send mail in the browser (port 2096) | ~40 MB | | `dav` | Calendars & contacts | CalDAV/CardDAV for every mailbox (Radicale), port 2080 | ~40 MB | | `dns` | DNS server | PowerDNS: customers' zones, DNSSEC, wildcard certificates | ~30 MB | | `waf` | Web application firewall | ModSecurity with the OWASP Core Rule Set, enabled per website | ~130 MB | | `apache` | Apache (.htaccess) | Lets websites switch to Apache mode so `.htaccess` files work | ~40 MB | | `ftp` | FTP server | Pure-FTPd with TLS, for customers who still use FTP | ~10 MB | | `postgres` | PostgreSQL | PostgreSQL databases for applications | ~120 MB | | `mongo` | MongoDB | MongoDB databases for Node.js and other applications | ~250 MB | | `adminer` | Adminer | Web interface for MariaDB, PostgreSQL and MongoDB | ~20 MB | | `docker` | Docker | Runs App Store applications and Dockerfile deploys, isolated per website | ~150 MB | | `storage` | S3 object storage | S3-compatible buckets for customers | ~150 MB | Dependencies are added for you: - `webmail` and `dav` need the mail server, so `mail` is installed with them. - `storage` needs Docker, so `docker` is installed with it. `storage` requires a Pro license; on the Free plan it is skipped. Apache is also installed automatically the first time a website switches to Apache mode, so you rarely need to add it by hand. ## Install components from the panel 1. Sign in as an administrator and go to **Components** (under **Server** in the menu). 2. Components are grouped as **Email**, **Web & security**, **Databases** and **Applications**. Installed ones are marked **installed**. 3. Tick the components you want. The panel shows about how much extra memory they need. 4. If you selected the mail server, enter the **Mail server hostname**, for example `mail.example.com`. It must resolve to this server; set its reverse DNS (PTR) at your VPS provider. 5. If you selected the DNS server, enter the **Nameservers**, for example `ns1.example.com, ns2.example.com`. Create them as glue records (A records pointing to this server's IP) at the registrar of that domain. 6. Click **Install N component(s)**. The installation runs as one background task. You can close the window: follow it under **Tasks**. Components that are already installed are skipped. If one fails, the others continue and the task lists what was not installed; you can retry it. Some pages also offer to install their component directly, for example **Install mail server** on the **Email** page or **Install FTP server** on the **FTP / SFTP** page. ## Choose components at install time ### Profiles | Profile | Components | | --- | --- | | `web` (default) | None: the web stack only | | `full` | `mail`, `webmail`, `dav`, `dns`, `waf`, `apache`, `ftp`, `postgres`, `adminer`, `docker` | ```bash curl -fsSL https://get.zopanel.net | sudo bash -s -- --profile full \ --mail-hostname mail.example.com \ --nameservers ns1.example.com,ns2.example.com ``` ### Individual components Add components to any profile with `--with`, comma separated: ```bash curl -fsSL https://get.zopanel.net | sudo bash -s -- --with waf,docker,postgres ``` `mongo` and `storage` are not part of `full`; add them with `--with`. ### What the installer needs | Component | Flag | If missing | | --- | --- | --- | | `mail`, `webmail` | `--mail-hostname mail.example.com` | Skipped with a message. If `--hostname` has at least three labels (for example `srv1.example.com`), it is used as the mail hostname. | | `dns` | `--nameservers ns1.example.com,ns2.example.com` | Skipped with a message | The installer does not install components itself. It queues them, and the panel installs them as a background task right after it starts, visible under **Tasks**. If the installer restarts the server (for a new kernel), the components are installed after the restart. If the server restarts in the middle, the task resumes. ## Ports opened Components open their ports in the UFW firewall when installed. If your provider has its own firewall, open them there too. | Component | Ports | | --- | --- | | Mail server | 25, 465, 587 (SMTP), 143, 993 (IMAP), 110, 995 (POP3) | | Webmail | 2096 | | Calendars & contacts | 2080 | | DNS server | 53 TCP and UDP | | FTP server | 21 and passive ports 30000-30100 | | Adminer | 8889 | ## After installing | Component | Where to use it | | --- | --- | | Mail server, webmail, calendars & contacts | **Email** page: enable email for a domain, create mailboxes, check DNS records and deliverability | | DNS server | **DNS** page: zones, records, DNSSEC, DNS cluster | | Web application firewall | **Security** page for all websites, and each website's **Tools** tab; start with **Detect only (log)** for a few days, then switch to blocking | | Apache (.htaccess) | A website's **Tools** tab: .htaccess support (Apache mode) | | FTP server | **FTP / SFTP** page: FTP accounts restricted to one folder | | PostgreSQL, MongoDB, Adminer | **Databases** page | | Docker | **App Store**, and Docker or Dockerfile deploys for websites | | S3 object storage | **S3 storage** page: buckets and access keys | ## Plan memory before you install Every component uses memory all the time, which is then not available for websites. On a 1-2 GB server, install only what you use. The **Tuning** page and the **PHP performance** card in **Settings → System** show how memory is shared between the system and customers after you add components. ## Websites ### Websites and PHP Source: https://zopanel.net/docs/hosting Every website in ZoPanel belongs to one hosting account and lives in `/home//domains//`. nginx serves it, and PHP runs in the account's own isolated PHP-FPM pool, so one customer's code never shares a PHP process with another's. ## Create a website 1. Open **Websites** and click **New website**. 2. Enter the **Domain** without `http://` (for example `example.com`). Leave **Also serve www.example.com** ticked to add the `www` name as an alias. 3. Choose the **Website type**: | Type | What you get | | --- | --- | | Git deploy | Pulls a repository, detects the framework and deploys it (see [Git deploy](/docs/git-deploy)). | | PHP | A PHP application in `public_html`. | | WordPress | A PHP website with WordPress installed automatically (see [WordPress Toolkit](/docs/wordpress)). | | Laravel | A PHP website whose document root is `public_html/public`. | | HTML | Static files only, no PHP. | | Node / Proxy | nginx forwards requests to an application listening on `127.0.0.1` (see [Node.js and Python apps](/docs/apps-node-python)). | 4. For PHP types, pick the **PHP version**. The list shows the versions installed on the server; ZoPanel supports PHP 7.4, 8.0, 8.1, 8.2, 8.3 and 8.4, and administrators install them under **Runtimes**. 5. Keep **Free SSL (Let's Encrypt)** on. The certificate is issued as soon as the domain points to the server (see [SSL certificates](/docs/ssl)). 6. Click **Create website**. Point the domain's A (and AAAA, if you use IPv6) record to the server's IP before you expect SSL to work. Administrators and resellers can choose the **Owner** account in the same dialog. The account's package limits how many websites it may have. ## Domains and aliases Open the website (**Websites → Manage**) and go to the **Domains** tab to add more names that serve the same files, for example `www.example.com` or a second brand domain. - Up to 20 aliases per website. - A domain can only be used once on the server. A domain related to one owned by another account (for example a subdomain of someone else's domain) is refused. - After you save a change, the Let's Encrypt certificate is re-issued automatically to cover the new list of names. - You cannot remove an alias that still has an email domain with mailboxes: delete its email domain first on the **Email** page. ## PHP version and document root The **PHP & config** tab holds the runtime settings: | Field | Meaning | | --- | --- | | PHP version | Any installed version, or **No PHP (static / proxy)**. You can change it at any time; the account's PHP-FPM pool is updated for you. | | Rewrite rules | **Standard**, **WordPress** or **Laravel**. The WordPress rules also block `xmlrpc.php`, PHP files in `wp-content/uploads` and direct access to `wp-config.php`, `readme.html` and `license.txt`. | | Document root | Relative to the domain folder and always inside `public_html`, for example `public_html` or `public_html/public`. | | Application port | Reverse-proxy port. Set it to `0` to turn proxy mode off. | On websites without PHP, nginx returns 403 for `.php`, `.phtml`, `.phar` and `.inc` files instead of showing their source. On every website, nginx denies hidden files (except `.well-known`) and common backup or dump files such as `.sql`, `.env`, `.bak` and `.log`. The memory limit and maximum execution time come from the account's package. Other options, such as `upload_max_filesize` or `date.timezone`, are set per website in **Tools → PHP settings for this website** (see [Website tools](/docs/website-tools)). Uploads are limited to 256 MB per request on every website. ## Apache mode for .htaccess ZoPanel serves sites with nginx by default, so `.htaccess` files are ignored. Sites moved from cPanel or DirectAdmin, and applications that ship `.htaccess` rules, can switch to Apache mode: 1. Open the website's **Tools** tab. 2. Turn on **.htaccess support (Apache mode)**. The first time, Apache is installed on the server (about 30 seconds). nginx keeps handling SSL, the firewall, redirects and common static files; Apache on `127.0.0.1` applies `.htaccess` and passes PHP to the account's own PHP-FPM pool. What `.htaccess` may contain in Apache mode: - **Supported:** rewrites, redirects, access rules (`Require`, `Deny`/`Allow`), password protection, `ErrorDocument`, headers, expiry, types and `SetEnv`. - **Ignored for security:** `Options`, `SetHandler`/`AddHandler` and similar lines. They are noted in the site's Apache log instead of breaking the site. - **Ignored:** `php_value` and `php_flag`. Set PHP options in **PHP settings for this website** instead (written to `.user.ini`). The page cache is off in Apache mode. Apache mode is available for PHP and static websites, not for proxy websites. ## Page cache PHP websites can be served from nginx's page cache, which speeds WordPress up many times: 1. Open the **PHP & config** tab. 2. Turn on **Page cache**. Pages are cached for 10 minutes. ZoPanel never caches: - requests other than GET and HEAD (for example POST); - visitors with a WordPress login, password-protected post, comment author, WooCommerce cart or session cookie, or a `laravel_session`, `PHPSESSID` or `XSRF-TOKEN` cookie; - URLs containing `/wp-admin`, `/wp-login.php`, `/wp-json`, `/xmlrpc.php`, `/cart`, `/checkout`, `/my-account`, `/feed` or `sitemap`; - URLs with a query string (search, filters, pagination). A query made only of ad-tracking parameters such as `utm_*`, `fbclid` or `gclid` counts as no query, so campaign visitors still get cached pages. Responses carry an `X-Cache` header (`HIT`, `MISS`, `BYPASS`…) so you can check the cache with: ```bash curl -sI https://example.com/ | grep -i x-cache ``` To clear the cache, click **Purge cache** in the same card. On WordPress websites with the cache on, ZoPanel also installs a small must-use plugin (*ZoPanel page cache*) that purges the site's pages whenever a post, comment, menu, theme or plugin changes, so edits appear at once. The plugin is removed when you turn the cache off. ## Static and proxy websites - **HTML** websites serve files from the document root with long browser caching for images, CSS, JavaScript and fonts. - **Node / Proxy** websites pass every request to `127.0.0.1:`. For customer accounts, ZoPanel assigns the port of each website; only administrators can choose another one. Use the **Deploy** tab to build and run the application (see [Node.js and Python apps](/docs/apps-node-python)). ## Request rate limit The **Request rate limit** card on the **PHP & config** tab stops bots and floods from one IP address. Choose **Off**, **Light (20/s)**, **Medium (8/s)** or **Strict (2/s)**; static files are not counted and excess requests get HTTP 429. ## Custom nginx directives Administrators see an **Advanced** tab where they can add **Custom nginx directives** to the website's server block. The configuration is tested before it is applied (**Test & save**), so a typo never takes nginx down. ### SSL certificates Source: https://zopanel.net/docs/ssl ZoPanel gives every website a free Let's Encrypt certificate and renews it for you. You manage certificates in the website's **SSL** tab (**Websites → Manage → SSL**). ## Before you start - The administrator must set an **Administrator email** in **Settings**. Let's Encrypt registration uses it, and no certificate can be requested without it. - Every name on the certificate (the domain and its aliases) must resolve to this server. - Port 80 must be reachable from the Internet: certificates are validated over HTTP, through a challenge path that keeps working even when the site redirects to HTTPS, redirects everything elsewhere or is password-protected. ## Automatic certificates When you create a website with **Free SSL (Let's Encrypt)** on, ZoPanel tries to issue the certificate right away. If the domain does not point to the server yet, the website still works over HTTP and the task log says so. You do not need to come back and retry: every hour, ZoPanel looks for active websites with automatic SSL but no certificate and checks their DNS. As soon as the domain resolves only to this server, the certificate is issued. Aliases that still point elsewhere are left out, so they cannot make the whole order fail; they are added on the next issue. After a failed attempt, the next one for that website waits 2, 4, 8 hours and so on, up to a day, so Let's Encrypt rate limits are never wasted. To issue or re-issue a certificate by hand, click **Issue certificate** (or **Re-issue**) on the **SSL** tab. The task log shows each step and a clear message when validation fails, for example when the domain does not resolve or port 80 is closed. ## Redirect HTTP to HTTPS Once a certificate is installed, the **SSL** tab shows the issuer, the expiry date and two switches: | Switch | Effect | | --- | --- | | **Redirect HTTP to HTTPS** | Every HTTP request gets a 301 redirect to HTTPS. ZoPanel also sends `Strict-Transport-Security: max-age=31536000`, so browsers remember to use HTTPS for one year. | | **Auto renew** | Lets ZoPanel renew the certificate (on by default for Let's Encrypt). | Because of the HSTS header, only turn on the redirect when you intend to keep HTTPS on that domain. ## Renewals ZoPanel checks certificates every hour and renews Let's Encrypt certificates that expire within 30 days. If a renewal fails, it is retried after 1, 2, 4 and then 8 days (never more than a week apart), because Let's Encrypt limits failed attempts. You are told when something needs attention: - **SSL renewal failed**: sent to the administrator and to the website's owner, with the error. - **SSL expiring soon**: sent once a day when a certificate expires within 14 days, which catches failing renewals and certificates installed by hand. Customers choose these alerts in **My account → Notifications**. The dashboard also shows **SSL expiring soon** for certificates that expire within 14 days. The most common reason a renewal fails is a domain that no longer points to this server. Fix DNS, then click **Re-issue**. ## Wildcard certificates A wildcard certificate (`*.example.com`) covers every subdomain. Let's Encrypt only issues wildcards through DNS validation, so ZoPanel needs to control the domain's DNS: 1. The administrator installs the DNS server (see [DNS](/docs/dns)). 2. Create the zone for the domain on the **DNS** page and set this server's nameservers at the registrar. 3. On the website's **SSL** tab, tick **Also \*.example.com (wildcard)** and click **Issue certificate**. The certificate covers `example.com` and `*.example.com`. Aliases already covered by the wildcard (for example `www.example.com`) are dropped from the order, because Let's Encrypt refuses redundant names; other aliases are kept. Renewals keep the wildcard. If the zone is not hosted on this server, ZoPanel refuses the request with a message that points you to the DNS page. ## Upload your own certificate To use a certificate from another provider (for example an EV or OV certificate): 1. On the **SSL** tab, find **Custom certificate**. 2. Paste the **Certificate (incl. chain)** in PEM format: your certificate first, then the intermediate certificates. 3. Paste the **Private key** in PEM format. 4. Click **Install certificate**. ZoPanel checks that the key matches the certificate, that the certificate is valid for the website's domain and that it has not expired. Only the certificates are written to the world-readable chain file; the private key is stored separately with restricted permissions. A custom certificate turns **Auto renew** off, so ZoPanel does not replace it with Let's Encrypt. Renew it with your provider and install the new one before it expires; the 14-day expiry alert reminds you. If the domain's zone is hosted on this server, note that new zones get a CAA record that allows only Let's Encrypt (`0 issue "letsencrypt.org"`). Add a CAA record for your other certificate authority on the **DNS** page before ordering from it. ## Disable SSL **Disable SSL** removes HTTPS from the website and turns the HTTP-to-HTTPS redirect off. Visitors who already received the HSTS header keep trying HTTPS until it expires, so prefer re-issuing a certificate over disabling SSL on a live site. ## The panel's own certificate The panel itself (port 8888) gets a trusted certificate from **Settings → Panel domain & SSL**. Point the panel domain to the server, then click **Issue certificate**. This certificate is also used by the mail server and webmail, and it is renewed automatically like website certificates. ### WordPress Toolkit Source: https://zopanel.net/docs/wordpress The **WordPress** tab of a website gathers everything you need to run WordPress: one-click login, safe updates, staging copies, security hardening, plugins and themes. It appears on every PHP website; ZoPanel reads the installation with wp-cli, so it also works for WordPress sites you uploaded or migrated yourself. ## Install WordPress Choose one of these: - **New website:** in **Websites → New website**, pick the **WordPress** type, fill in **Site title**, **Admin email**, **Admin username** and **Admin password**, then **Create website**. - **Existing PHP website:** on the **Overview** tab, under **Applications**, click **Install WordPress**. This card appears while the document root is in `public_html` and no application is installed yet. ZoPanel creates a database and installs the latest WordPress with wp-cli (in Vietnamese when you use the panel in Vietnamese). For a new website with SSL on, the certificate is issued first when the domain already points to the server, so WordPress starts on `https://`; on an existing website, WordPress uses `https://` if the site already has a certificate. The account's package must allow one more database. Tip: the admin username defaults to `admin`. Change it before you create the site; the security check flags an administrator named "admin". ## Log in without a password Click **Log in to WordPress**. ZoPanel opens wp-admin as the first administrator, using a one-time link that is valid for 60 seconds and works only once. No password is stored or sent. ## Security hardening The **Security** card checks the recommended settings: | Check | What it means | | --- | --- | | Theme/plugin editor disabled | `DISALLOW_FILE_EDIT` is set, so a stolen admin login cannot edit PHP files. | | Debug mode off | `WP_DEBUG` is off on the live site. | | wp-config.php readable only by the account | The file has mode 600. | | xmlrpc.php, PHP in uploads and wp-config.php blocked | The website uses the WordPress nginx rules. | | No administrator named "admin" | The first name bots try. | | Everything up to date | Core, plugins and themes. | Click **Fix** next to a single item, or **Apply all** to set every recommended option at once (it also rotates the security keys). Note that blocking `xmlrpc.php` disables apps and plugins that rely on XML-RPC. Other buttons on the card: - **Verify file integrity** compares core and plugin files with the official checksums and lists modified files. - **Rotate security keys** generates new salts in `wp-config.php`, which signs every user out. - **Reset password** sets a new password for any WordPress user. ## Updates The header shows whether a core update is available, and the **Plugins** and **Themes** tables show pending updates. - **Update all** runs a *safe* update: ZoPanel takes a snapshot of the files and database, updates, then checks that `/` and `/wp-login.php` still answer without a server error or a PHP "critical error". If the site broke, it is rolled back to the snapshot automatically. - To update only some plugins, select them in the table and click **Update**. - **Automatic updates** in the **Maintenance** card: choose **Safe automatic updates** to run the same snapshot-update-check-rollback cycle every night, or **Off (WordPress defaults)** to leave updates to WordPress. - The **Auto-update** switch on each plugin row controls WordPress's own automatic update for that plugin. If the site already had a problem before the update, ZoPanel notes it in the log and does not roll back for that reason. ## Staging copy and publishing to live A staging copy is a full copy of the site (files and database) on another domain, hidden from search engines, where you can test changes. 1. Click **Create staging**. The domain defaults to `staging.`; any domain or subdomain of the account works. 2. Point that name to the server. SSL is issued automatically once DNS resolves. 3. Work on the staging site. Its WordPress tab shows **staging of example.com**. 4. When ready, open the staging site's WordPress tab and click **Publish to live**. Publishing replaces the live site's files (except `wp-config.php`, `.user.ini` and `.maintenance`) and its database with the staging ones. ZoPanel replaces the staging URL with the live URL throughout the database (a wp-cli search-replace), keeps the live site's search-engine visibility setting and flushes caches. A snapshot of the live site is taken first: if the live site breaks after publishing, it is restored automatically. **Clone** makes an independent copy on another domain instead (files, database, URLs replaced), for example to start a new site from an existing one. Staging copies and clones count toward the package's website and database limits. ## Plugins and themes The **Plugins** table lists every plugin with its version, status and pending update. Select plugins to **Update**, **Activate**, **Deactivate** or delete them. The **Themes** list shows the active theme and lets you **Activate** another one. ## Maintenance card | Option | Effect | | --- | --- | | Visible to search engines | WordPress's "Search engine visibility" setting. | | Maintenance mode | Shows WordPress's maintenance page to visitors. | | Debug logging (wp-content/debug.log) | Turns on `WP_DEBUG` and `WP_DEBUG_LOG`, never displaying errors to visitors. Turn it off when you are done. | | Server runs scheduled tasks (every 5 min, not on visits) | See below. | | Flush caches | Runs `wp cache flush` and deletes all transients. | ### Server runs scheduled tasks WordPress normally runs its scheduled tasks (scheduled posts, shop emails, plugin jobs) from visitor requests by calling its own `wp-cron.php`. Quiet sites then run them late or never, busy sites pay for an extra PHP request, and if the domain resolves slowly each call can hold a PHP worker for 10 to 20 seconds. When you turn this option on, ZoPanel: 1. sets `DISABLE_WP_CRON` to `true` in `wp-config.php`; 2. creates a systemd timer that runs the due tasks every 5 minutes with wp-cli (`wp cron event run --due-now`), as the hosting account, inside its resource limits and at low CPU and disk priority. Sites are spread over the interval. Turning it off removes the timer, and removes `DISABLE_WP_CRON` only if ZoPanel added it. A site that had already disabled wp-cron itself keeps its own setting. ## Caching for WordPress Two caches work together. Both are on the **PHP & config** tab: - **Page cache** serves whole pages from nginx to anonymous visitors. Logged-in users, carts, checkout and admin pages are never cached. A small must-use plugin purges the site's cached pages whenever content changes, and **Purge cache** clears them by hand. See [Websites and PHP](/docs/hosting#page-cache). - **Redis object cache** keeps database query results in Redis (with the Redis Object Cache plugin), which helps wp-admin, WooCommerce and logged-in users. Redis must be installed on the server under **Databases**; ZoPanel creates a private Redis database for the site and configures the plugin. ## WordPress not detected? If the tab says WordPress is not installed, check that WordPress is in the website's document root (**PHP & config → Document root**), then reload the page. ### Git deploy Source: https://zopanel.net/docs/git-deploy Git deploy builds and runs your code straight from a repository. ZoPanel detects the framework, proposes install, build and start commands, builds in a sandbox limited to the account's CPU and memory, and only switches traffic to the new release after it passes a health check. If anything fails, the previous release keeps running. ## Create a website from Git 1. Open **Websites → New website** and choose the **Git deploy** type. 2. Enter the **Repository URL**, the **Branch** (default `main`) and, for monorepos, the **Root directory** (for example `apps/web`). 3. Keep **Free SSL (Let's Encrypt)** on and click **Create website**. The first deployment starts immediately. When it has finished, ZoPanel issues the SSL certificate in the same task, so the two never collide. You can also deploy into an existing website from its **Deploy** tab. ### Repository URLs | Form | Example | | --- | --- | | Public HTTPS | `https://github.com/owner/repo` | | SSH (private repositories) | `git@github.com:owner/repo.git` or `ssh://git@git.example.com:2222/owner/repo` | The repository host must be a public address. For a private repository, open **Deploy → Deploy key (private repositories)**, click **Show deploy key**, add that public key to the repository as a read-only deploy key, and use the `git@…` URL. Other sources in the **Source** list: **Uploaded files (File Manager)**, where you upload the project to `domains//source` with the File Manager or SFTP, and **Ready-made Docker image**. ## Framework detection On every deploy (unless you turn **Auto-detect** off), ZoPanel inspects the repository and fills in the build settings. The first match wins: | Found in the repository | Detected as | | --- | --- | | `composer.json` (and no Node server framework) | Laravel (`artisan` or `laravel/framework`), Symfony, or PHP (Composer). Front-end assets are built with npm when `package.json` has a `build` script. | | `package.json` | Next.js, Nuxt, Remix / React Router, SvelteKit, Astro, NestJS, Gatsby, Angular, Create React App, Docusaurus, VitePress, Vite (React/Vue/Svelte), or a Node.js server (Express, Fastify, Koa, Hono…) | | `wp-config-sample.php` + `wp-login.php` | WordPress | | `Gemfile` | Ruby on Rails, Rack (Sinatra, Hanami…) or Ruby | | `pom.xml`, `build.gradle` | Java / Spring Boot | | `*.csproj` | .NET / ASP.NET Core | | `manage.py`, `requirements.txt`, `pyproject.toml`, `Pipfile` | Django, FastAPI, Flask or Python | | `go.mod` | Go | | `index.php` / `index.html` | PHP / static HTML | | `Dockerfile` only | Dockerfile (container) | A few details worth knowing: - **Package manager:** `pnpm-lock.yaml` → `pnpm install --frozen-lockfile`, `yarn.lock` → `yarn install --frozen-lockfile`, `package-lock.json` → `npm ci`, otherwise `npm install`. - **Node.js version:** taken from `.nvmrc`, `.node-version` or `engines.node`. If that major version is not installed, the newest installed one is used. - **Static exports:** Next.js with `output: 'export'`, Astro without `@astrojs/node`, SvelteKit with `adapter-static`, Vite, Angular and similar builds are served as static sites from their output folder. - **Procfile:** a `web:` line always provides the start command. - **Laravel:** an `APP_KEY` is generated if missing, `storage` and `.env` are kept between releases, and sessions and cache use the account's Redis when it is available. Click **Analyze repository** to see the detection before you deploy, then **Use these settings and customize** if you want to change them. ## Build and run settings The **Build & run** card shows what will run: | Field | Notes | | --- | --- | | Runtime / Version | node, python, go, php, ruby, java, dotnet, static or docker. **Latest installed** uses the newest version on the server. | | Type | **Server app (process)**, **Static site**, **PHP (PHP-FPM)** or **Container (Dockerfile)**. | | Install command | For example `npm ci` or `composer install --no-dev --optimize-autoloader --no-interaction`. | | Build command | For example `npm run build`. Chain several commands with `&&` on a single line. | | Start command | Server apps only. The app must listen on `127.0.0.1:$PORT`. | | Output directory / Document root | Folder served for static and PHP sites, such as `dist` or `public`. | | Persistent paths | Kept between releases, such as `storage`, `uploads` or `.env`. | | Health check path | Must answer with a status below 500 before the release goes live. Default `/`. | **Environment variables** are available during the build and at runtime; `PORT` is set automatically. Use **Paste .env** to add many at once (up to 200 variables, single-line values). Container (Dockerfile) mode must first be enabled once on the server by an administrator: ```bash zopanel ctl feature enable custom-docker ``` ## How a deployment runs 1. The code is fetched into a new release folder: `domains//releases//`. 2. Install and build commands run as the hosting account through systemd, inside the account's CPU and memory limits, unable to write outside its home. 3. Server apps start in the idle slot on their own port. ZoPanel waits up to 90 seconds for the health check path to answer. 4. nginx switches to the new release (a graceful reload), then the previous process is stopped. `current` points to the live release. Each deployment is listed under **Deployments** with its commit, trigger (**Manual**, **Git push** or **Created**) and status. If a deployment fails, the owner gets a **Deployment failed** notification. ## Automatic deploys on push 1. On the **Deploy** tab, turn on **Deploy when main is pushed** in the **Automatic deploys** card. 2. Copy the **Payload URL** (`https://:8888/api/hooks/deploy/`) and the **Secret**. 3. Add the webhook in your Git host: - **GitHub:** Settings → Webhooks → paste the URL, content type `application/json`, and the secret. - **GitLab / Gitea:** paste the URL and put the secret in **Secret token**. ZoPanel verifies the signature (GitHub's `X-Hub-Signature-256`) or the token header (GitLab, Gitea); the secret is never accepted in the URL. Only pushes to the configured branch deploy. Duplicate deliveries are ignored, and at most one webhook deploy starts per 15 seconds, never while another deployment is running. GitHub's ping event answers `pong`. **Regenerate secret** replaces the secret; update the webhook in your Git host afterwards. ### Deploy from CI with an API token With a Pro license, create an API token in **My account → API tokens** and trigger a deployment from any CI system: ```bash curl -X POST \ -H "Authorization: Bearer zpat_..." \ https://panel.example.com:8888/api/sites//app/deploy ``` ## Rollback The **Releases** card keeps the last 5 builds. Click **Rollback** on any of them: server apps are started in the idle slot and switched over after the health check, exactly like a deployment, so rolling back causes no downtime. ## Logs - **Deploy tab → Application output** shows the latest output of the running app; turn on **Live** to follow it. - Each deployment's build log is kept in **Tasks**. - The website's **Logs** tab shows the nginx access and error logs, and **Run diagnostics** explains recent errors. ### Node.js and Python apps Source: https://zopanel.net/docs/apps-node-python Server applications (Node.js, Python, Go, Ruby, Java, .NET) run as a service of the hosting account, behind nginx, which handles the domain, SSL and static files. ZoPanel builds and starts them through the website's **Deploy** tab, keeps them running, and restarts them if they crash. ## Install the runtimes Administrators install language runtimes under **Runtimes**: | Runtime | Versions offered | Used for | | --- | --- | --- | | Node.js | 24, 22, 20, 18 (several side by side) | Next.js, Nuxt, NestJS, Express and front-end builds. pnpm and yarn are included. | | Python | The system Python 3, with virtual environments | Django, Flask, FastAPI | | Go | Latest | Go services | | Composer | Latest | Laravel and Symfony | | Ruby, Java, .NET | System packages (.NET 8.0) | Rails/Rack, Spring Boot, ASP.NET Core | If a project asks for a Node.js major version that is not installed (in `.nvmrc`, `.node-version` or `engines.node`), the newest installed version is used. ## Create the website You have two options: - **Git deploy** (recommended): in **Websites → New website**, choose **Git deploy** and enter the repository. See [Git deploy](/docs/git-deploy). - **Node / Proxy**: choose **Node / Proxy** to create a website that forwards every request to an application port, then set up the app on the **Deploy** tab with **Uploaded files (File Manager)**: upload your project to `domains//source` with the File Manager or SFTP and click **Deploy**. ## Ports Your app must listen on `127.0.0.1` at the port given in the `PORT` environment variable. ZoPanel sets it for you: - Each website gets its own base port, shown as **Internal port** on the **Deploy** tab (`127.0.0.1:`). - For blue/green releases, the second slot uses the base port plus 10000. Never hard-code a port: always read `PORT`. - On customer accounts, the proxy port of a website is fixed by ZoPanel and cannot point to another local service. Only administrators can set a different **Application port** on the **PHP & config** tab. Examples: ```js // Node.js (Express) app.listen(process.env.PORT, "127.0.0.1"); ``` ```python # Python: bind the server to $PORT, e.g. in the start command # .venv/bin/gunicorn myproject.wsgi:application --bind 127.0.0.1:$PORT ``` ## Start commands ZoPanel proposes the start command from the project. You can change it in **Build & run → Start command** (one line; chain steps with `&&`). | Framework | Proposed start command | | --- | --- | | Next.js | `npm run start` (or `npx next start`) | | Nuxt | `node .output/server/index.mjs` | | NestJS | `npm run start:prod` or `node dist/main` | | Express, Fastify, Koa, Hono | `npm run start`, or `node
` / `server.js` / `index.js` / `app.js` | | Django | `.venv/bin/gunicorn .wsgi:application --bind 127.0.0.1:$PORT` | | FastAPI | `.venv/bin/uvicorn main:app --host 127.0.0.1 --port $PORT` | | Flask | `.venv/bin/gunicorn app:app --bind 127.0.0.1:$PORT` | | Go | `./bin/` (built with `go build -o bin/…`) | | Any project with a Procfile | the `web:` line | For Python, the install command creates a virtual environment in `.venv` and installs `requirements.txt`, `pyproject.toml` or `Pipfile`; gunicorn or uvicorn is added when it is missing. For Django, add your domain to `ALLOWED_HOSTS` (or read it from an environment variable). Put secrets and settings in **Environment variables**. They are available during the build and at runtime, and members with view-only access cannot see their values. ## Zero-downtime (blue/green) releases Every server app has two slots. A new release: 1. is built in its own release folder; 2. starts in the idle slot, on that slot's port; 3. must answer on the **Health check path** (default `/`) with a status below 500 within 90 seconds; 4. only then receives traffic: nginx is reloaded gracefully to the new slot, and the old slot is stopped. If the new release fails to build, exits during startup or never becomes healthy, the running release is not touched. **Rollback** in the **Releases** card (the last 5 builds are kept) uses the same mechanism. Because two releases briefly run side by side, keep state that must survive a release in the database, Redis or a **Persistent path** (for example `uploads` or `.env`), not inside the release folder. ## Use every CPU core (Node.js) Node.js runs on a single core. For Node.js server apps, the **Use every CPU core** card can run one process per CPU of the package without code changes, which serves several times more requests. Turn on **One process per CPU core (redeploys to apply)** only if the app keeps no state in memory: keep sessions, cache and websockets in Redis or a database. ## Start, stop and restart The status card on the **Deploy** tab shows the app's state, framework, runtime and internal port, with buttons to **Deploy now**, **Restart**, stop and start the app. The service restarts automatically if the process exits. Resource usage counts toward the account's package: memory, CPU and processes of all websites and apps are shown under **Resources**. ## Logs - **Application output** on the **Deploy** tab shows the latest 400 lines written by the app (stdout and stderr, both slots). Turn on **Live** to follow it. - The build log of every deployment is in **Tasks**. - The **Logs** tab shows nginx's access and error logs. A 502 error there usually means the app is not listening on `$PORT`; **Run diagnostics** detects this and suggests a fix. ## Troubleshooting | Symptom | What to check | | --- | --- | | "The app did not respond on port … within 90s" | The app must listen on `127.0.0.1:$PORT`, not a fixed port. | | "The app exited during startup" | Read **Application output**: usually a missing environment variable or dependency. | | "Node.js is not installed — install it under Runtimes" | Ask the administrator to install a Node.js version. | | Health check returns 404 or a redirect | Any status below 500 passes. A 5xx means the app is broken; set **Health check path** to a route that answers quickly. | ### Website tools Source: https://zopanel.net/docs/website-tools The **Tools** tab of a website (**Websites → Manage → Tools**) groups the settings you would otherwise write by hand in `.htaccess` or nginx files. Every change is validated and the nginx configuration is tested before it goes live; if a change is rejected, the previous settings are kept. ## Redirects Send visitors from an old address to a new one. 1. In **Redirects**, click **Add redirect**. 2. Fill in **From path** (for example `/old-page`) and **To URL or path** (for example `https://example.com/new-page` or `/new-page`). 3. Choose the status code: **301** (permanent, the default), **302**, **307** or **308**. 4. Optionally turn on **Whole folder** to redirect everything under the path, and **Keep rest of path** to append the rest of the requested path to the target. 5. Click **Save**. | From path | Whole folder | Keep rest of path | Result | | --- | --- | --- | --- | | `/old-page` | off | – | Only `/old-page` is redirected. | | `/blog` | on | off | `/blog` and everything under it go to the same target. | | `/blog` | on | on | `/blog/post-1` → `/post-1` (query string kept). | | `/` | on | on | The whole site moves to a new domain, keeping paths. | Redirecting the whole site with `/` and **Whole folder** keeps SSL renewal working, because the certificate validation path is never redirected. A website can have up to 500 redirects. ## Password-protected directories Visitors must log in (HTTP basic authentication) to open these folders. PHP keeps running inside them, so you can protect an admin area of an application. 1. In **Password-protected directories**, click **Protect a directory**. 2. Enter the **Directory** as a URL path, for example `/admin` (use `/` for the whole site). 3. Change the **Prompt text** if you like (default `Restricted`). 4. Add one or more users with a **User name** and password (8 to 128 characters). Use **Add user** for more. 5. Click **Save**. When you edit a directory later, leave **Password (empty = keep)** blank to keep a user's current password. Passwords are stored as SHA-512 crypt hashes, never in plain text. Limits: 50 protected directories per website and 100 users per directory. ## Hotlink protection Stop other websites from embedding your images, videos and downloads and using your bandwidth. 1. In **Hotlink protection**, turn on **Enabled**. 2. In **Also allow these domains**, list partner sites or CDNs, separated by commas (for example `partner.com, cdn.example.com`). Their subdomains are allowed too. 3. In **File types**, list the extensions to protect, or leave it empty for the defaults: `jpg, jpeg, png, gif, webp, avif, svg, bmp, mp4, webm, mp3, ogg, pdf, zip`. 4. Click **Save**. Requests from your own domain, its aliases and their subdomains are always allowed, as are direct visits (no referrer). Other referrers get HTTP 403. Page and code files (`php`, `html`, `htm`, `js`, `css`, `json`, `xml`, `txt`) cannot be hotlink-protected, so you cannot break your own site by accident. This card is not shown for proxy websites. ## Custom error pages Show your own page instead of the default error page. Enter a path inside the document root for any of the codes **403**, **404**, **500**, **502** and **503**, for example: ```text 404 → /errors/404.html 503 → /errors/maintenance.html ``` Leave a field empty to keep the default page. Upload the files with the File Manager first. Custom error pages are not available for proxy websites; your application answers its own errors there. ## PHP settings for this website PHP websites show **PHP settings for this website**. The card reminds you of the limits that come from the package (memory limit and maximum execution time) and the upload limit of 256 MB. | Setting | Accepted values | Example | | --- | --- | --- | | `upload_max_filesize` | 1M to 256M | `128M` | | `post_max_size` | 1M to 256M | `128M` | | `max_input_vars` | 100 to 999999 | `5000` | | `max_input_time` | `-1` or 0 to 9999 seconds | `60` | | `session.gc_maxlifetime` | seconds | `1440` | | `date.timezone` | a valid time zone | `Asia/Ho_Chi_Minh` | | `short_open_tag` | default, On or Off | | | `output_buffering` | default, Off or 4096 | | Empty fields keep PHP's default. ZoPanel writes the values to `.user.ini` in the document root, inside a block marked `; BEGIN ZoPanel` … `; END ZoPanel`; lines you added to that file yourself are kept. The account's PHP-FPM pool is reloaded so the change applies at once. `.user.ini` cannot be downloaded by visitors. In Apache mode, `php_value` and `php_flag` lines in `.htaccess` are ignored: use this card instead. ## Web application firewall If the administrator has installed the firewall, each website shows a **Web application firewall** card. It uses ModSecurity with the OWASP Core Rule Set to stop SQL injection, XSS, file inclusion, remote code execution and vulnerability scanners. | Mode | Effect | | --- | --- | | **On (block attacks)** | Matching requests are blocked. | | **Detect only (log)** | Matching requests are logged but allowed. | | **Off** | The firewall does not inspect this website. | Recommended rollout: 1. Set **Detect only (log)** for a few days. 2. Review the events table (time, IP, request and reason). Each event is marked **blocked** or **logged**. 3. If a legitimate request was flagged (a page builder or an import, for example), click **Allow this**. The matching rule IDs are added to **Allowed rules** for this website only. Click a rule in that list to **Remove** the exception. 4. Switch to **On (block attacks)**. Static files such as images, CSS and JavaScript skip the firewall, which saves CPU. **For administrators:** install the firewall in **Security → Web application firewall → Install firewall**, or from **Components**. It is loaded only while at least one website uses it (about 25 MB of RAM per nginx worker). **Apply to all websites** sets one mode on every website at once, and the card shows how many websites block, only log, or have it off. ## .htaccess support (Apache mode) The **.htaccess support (Apache mode)** card on the **Tools** tab switches the website to Apache mode, so `.htaccess` rules work as on cPanel or DirectAdmin. What is supported and what is ignored is described in [Websites and PHP](/docs/hosting#apache-mode-for-htaccess). ## Mail, DNS & databases ### Email Source: https://zopanel.net/docs/email ZoPanel's mail server is built on Postfix, Dovecot (IMAP/POP3) and Rspamd, with DKIM signing, spam filtering and Sieve rules. Customers manage their own domains and mailboxes on the **Email** page; the administrator controls the server-wide settings. ## Install the mail server (administrator) 1. Open **Email** and fill in **Mail hostname**, a fully qualified name such as `mail.example.com`. It must resolve to this server. 2. Click **Install mail server**. You can also install it from **Components**, together with **Webmail** and **Calendars & contacts (CalDAV/CardDAV)**. 3. Ask your VPS provider to set the server IP's reverse DNS (PTR) to the mail hostname. Without it, much of your mail lands in spam. 4. Click **Install webmail** if it was not installed with the components. The installer opens the mail ports in the firewall. If ZoPanel detects that your provider blocks outgoing port 25, the Email page warns you: incoming mail works, but sending to other servers fails until the provider opens the port, or until you configure **Outgoing mail relay (smarthost)** (SendGrid, Mailgun, Amazon SES, Brevo…). Mail services use the panel's certificate: set **Settings → Panel domain & SSL** so mail clients see a trusted certificate. ## Enable email for a domain 1. On the **Email** page, click **Enable email for a domain**. 2. Choose one of your websites' domains and click **Enable**. 3. Publish the records shown in the **DNS records** card at your DNS provider. If you host the domain's DNS on this server and create its zone after enabling email, the zone is created with these records already in it; for an existing zone, add them on the **DNS** page. | Record | Name | Value | | --- | --- | --- | | MX | `example.com` | the mail hostname, priority 10 | | TXT (SPF) | `example.com` | `v=spf1 mx a ~all` | | TXT (DKIM) | `zp._domainkey.example.com` | the public key shown in the panel | | TXT (DMARC) | `_dmarc.example.com` | `v=DMARC1; p=quarantine; rua=mailto:postmaster@example.com` | Then click **Run check** in **Mail delivery check**. It compares public DNS with the expected MX, SPF, DKIM and DMARC records, checks reverse DNS (PTR) and outgoing port 25, and looks up the server IP on the Spamhaus, SpamCop and Barracuda blocklists, with a hint for each problem. ## Mailboxes 1. Open the domain and click **New mailbox**. 2. Enter the address, a password (a strong one is generated) and the **Quota (MB)** (default 1024, `0` = unlimited). 3. Copy the password from the confirmation: it is not shown again. The key icon changes a mailbox's password or quota. The number of mailboxes is limited by the account's package. ### Mail client settings The username is always the full email address. | Protocol | Port | Security | | --- | --- | --- | | IMAP | 993 | SSL/TLS | | IMAP | 143 | STARTTLS | | POP3 | 995 | SSL/TLS | | POP3 | 110 | STARTTLS | | SMTP (sending) | 465 | SSL/TLS | | SMTP (sending) | 587 | STARTTLS | Login always requires encryption. The server name is shown in **Email client settings** on the domain page. ### Webmail without a password Webmail (SnappyMail) runs on port 2096. In the mailbox list, the envelope icon **Open webmail (no password needed)** signs you in to that mailbox in a new tab. The panel never knows mailbox passwords: it uses a single-use link (valid for seconds) with a login code that opens only that mailbox. Users can also sign in directly at `https://:2096` with their address and password. ## Forwarders and catch-all In **Forwarders**, click **New forwarder**, enter the local part of the **Address** and one or more destinations in **Forward to**, separated by commas. To catch all mail sent to unknown addresses of the domain, leave the address empty (or enter `*`). The catch-all appears as `*@example.com` in the list. ## Rules and autoreply (Sieve) Click the filter icon next to a mailbox to open **Rules and autoreply**. Rules run when mail is delivered, before it reaches the inbox. - **Autoreply:** turn on **Send an automatic reply**, set the **Subject** and **Message**, an optional **From** and **Until** date, and how often the same sender gets a reply. - **Filters:** match **From**, **To / Cc** or **Subject** that **contains** or **is** a value, then **Move to folder**, **Forward a copy**, **Mark as read** or **Delete**. - **Senders & spam:** **Move spam to the Junk folder**, lists of senders to **Always allow** and to **Block** (deleted silently), one address or domain per line. The **Spam filter** card sets how strict filtering is for the whole domain: **Low**, **Medium (recommended)** or **High**. ## Mailing lists A mailing list is one address that delivers to every member. In **Mailing lists**, click **New list**, enter the **List address** and the **Members**. Turn on **Only members (and the senders below) can send to the list** to keep it closed, and list extra allowed senders. The original sender's signature is kept, so messages reach inboxes instead of spam. ## Calendars and contacts When **Calendars & contacts (CalDAV/CardDAV)** is installed, every mailbox gets its own calendars and address books at `https://:2080/`. Sign in with the mailbox address and password in your phone, Thunderbird or Outlook. Each user only sees their own collections. ## Sending limits Hacked websites are the usual source of spam on a hosting server, and one bad night can get the server's IP blocklisted for every customer. ZoPanel counts every recipient each account sends per hour, whether the mail comes from PHP `mail()` or from an SMTP login, and temporarily refuses mail over the limit. - Administrators set **Default per hour** in **Email → Sending limits** (500 by default; `0` = unlimited). The table shows each account's mail in the last hour, its limit and refusals in the last 24 hours. - Each package can override the limit with **Emails per hour** in **Packages** (empty = server default, `-1` = unlimited). - Customers see **Emails sent this hour** under **Resources**, and can be notified when the limit is reached. ### Why port 25 from a website or script is refused With **Block direct SMTP from accounts** on (the default), website code cannot connect to other mail servers on port 25, and unauthenticated SMTP from the server itself is refused. All mail must go through this server, where it is counted, DKIM-signed and logged, like cPanel's SMTP Restrictions. To send from an application, use PHP `mail()` or SMTP with a mailbox login: ```text Host: Port: 587 Encryption: STARTTLS Username: noreply@example.com Password: the mailbox password ``` ## Queue, logs and blocklists - **Delivery log** on each domain page lists recent mail sent and received, searchable by address or status. - Administrators also see the server-wide **Delivery log** and the **Mail queue**, where they can **Retry**, **Hold**, **Release**, view **Message headers**, **Retry all** or **Delete all deferred**. - ZoPanel alerts the administrator when the queue builds up (300 messages or more) and checks once a day whether the server IP is on a blocklist. If it is, find the sending account in **Sending limits**, stop the source, then request delisting on the list's website. ### DNS Source: https://zopanel.net/docs/dns ZoPanel can run an authoritative DNS server (PowerDNS) so that you and your customers manage zones next to the websites that use them. Hosting DNS here is optional, but it is required for wildcard SSL certificates. ## Install the DNS server (administrator) 1. Decide on your nameserver names, for example `ns1.yourcompany.com` and `ns2.yourcompany.com`. 2. At the registrar of `yourcompany.com`, create glue records (host records) for those names pointing to this server's IP. 3. In ZoPanel, open **DNS**, enter the **Nameservers** (1 to 6 names, separated by commas) and click **Install DNS server**. You can also install it from **Components**. The installer opens port 53 (TCP and UDP) in the firewall. If the DNS server stops answering, the DNS page shows **DNS server not answering**. ## Create a zone 1. On the **DNS** page, in **DNS zones**, **Choose a domain** from your websites and click **Add zone**. A zone can only be created for a domain that has a website in the account. 2. At the domain's registrar, set the nameservers shown under **Nameservers to set at the registrar**. A new zone already contains: | Name | Type | Value | | --- | --- | --- | | `@` | A | the server IP (or the account's dedicated IP) | | `www` | A | the same IP | | `@` | CAA | `0 issue "letsencrypt.org"` | | MX, SPF, DKIM, DMARC | | added when email is already enabled for the domain | The CAA record allows only Let's Encrypt to issue certificates for the domain. If you buy a certificate from another authority, add a CAA record for it first. ## Edit records Open a zone to edit its records in a table: **Name**, **Type**, **Priority**, **Value** and **TTL**. Click **Add record**, then **Save** to apply all changes at once. | Type | Value format | Example | | --- | --- | --- | | A | IPv4 address | `203.0.113.10` | | AAAA | IPv6 address | `2001:db8::10` | | CNAME | host name | `shop.example.net` | | MX | host name, with **Priority** | `mail.example.com`, priority `10` | | TXT | text | `v=spf1 mx a ~all` | | SRV | `weight port target`, with **Priority** | `5 5060 sip.example.com` | | CAA | `flags tag "value"` (tag `issue`, `issuewild` or `iodef`) | `0 issue "letsencrypt.org"` | | NS | host name (delegates a subdomain) | `ns1.other-dns.com` | Rules: - Use `@` for the zone apex and relative names (`www`, `mail`) for the rest. - NS and CNAME records are not allowed at the apex. - TTL is between 60 and 604800 seconds; the default is 3600. The SOA record and the zone's own NS records are managed by ZoPanel and are not shown. ## DNSSEC DNSSEC signs the zone so resolvers can detect spoofed answers. 1. Open the zone and turn on **DNSSEC**. 2. Copy the **DS record(s) for the registrar** shown below the switch. 3. Add them at the domain's registrar (usually under "DNSSEC" or "DS records"). To turn DNSSEC off later, remove the DS records at the registrar first and wait for their TTL to expire, then turn the switch off. Otherwise resolvers treat the domain as broken. ## Wildcard SSL With the zone hosted and delegated here, a website can get a `*.example.com` certificate: tick **Also \*.example.com (wildcard)** on the website's **SSL** tab. See [SSL certificates](/docs/ssl#wildcard-certificates). ## DNS cluster (administrator) Serve your zones from several ZoPanel servers so DNS keeps answering if one server is down. The **DNS cluster** card on the DNS page has three settings: | Setting | Meaning | | --- | --- | | **Secondary servers (IPs)** | Servers allowed to transfer every zone of this server. They are notified (NOTIFY) of each change. | | **Primary servers** | One line per primary: its IP and this server's nameserver name in its zones, for example `203.0.113.10 ns2.example.com`. Zones of the primaries are received automatically. | | **Cluster key (TSIG)** | A shared key (HMAC-SHA256) that signs zone transfers. | Recommended setup for two servers: 1. On the primary, click **Generate** next to **Cluster key (TSIG)** and copy the key: it is not shown again. 2. On the primary, add the secondary's IP to **Secondary servers (IPs)** and **Save**. 3. On the secondary, paste the same key, add the primary to **Primary servers** and **Save**. 4. Make sure the secondary's nameserver name (the one you entered on the secondary, for example `ns2.example.com`) is among the nameservers set at each domain's registrar, with a glue record pointing to the secondary. With a key, a primary serves its zones only to holders of the key, and a secondary only accepts zones signed with it, so nobody on the path can alter them. Without a key (**No key: transfers allowed by address**), transfers are allowed by IP address only. Deleting a zone on the primary does not delete it on the secondaries immediately. A secondary removes a zone its primaries no longer serve after three daily checks in a row; unreachable primaries change nothing. The card lists the zones received from primaries. ## Importing zones When you migrate an account from cPanel or DirectAdmin with **Migrate in**, its DNS zones are imported too, as long as the DNS server is installed here. ZoPanel's own web, mail (MX, SPF) and CAA records win, other records from the old zone are kept, and a zone that already exists on this server is left untouched. Without the DNS server, zones stay with the old provider and the import log says so. To copy a zone from any other provider, create the zone here and add its records in the editor before you switch nameservers. ## Websites behind Cloudflare ZoPanel does not manage Cloudflare DNS. If you proxy websites through Cloudflare, turn on **Websites behind Cloudflare** in **Security**: ZoPanel then takes the visitor's IP from the `CF-Connecting-IP` header, trusted only from Cloudflare's address ranges, so logs, statistics, rate limits and Fail2ban see real visitors. Automatic SSL waits until a domain resolves only to this server, so a domain proxied by Cloudflare is not picked up automatically. Use **Issue certificate** on the website's **SSL** tab, or install a certificate in **Custom certificate** (for example a Cloudflare Origin certificate). ### Databases Source: https://zopanel.net/docs/databases The **Databases** page lists every database of the account, whatever the engine. Each database gets its own user with full privileges on that database only, and Redis databases get their own isolated key namespace. ## Database engines (administrator) MariaDB is the default engine. Administrators install the others from the **Database servers** card on the **Databases** page, or from **Components**: | Engine | Port (local) | Notes | | --- | --- | --- | | MariaDB | 3306 | MySQL-compatible. Used by WordPress, Laravel and most PHP apps. | | PostgreSQL | 5432 | | | MongoDB | 27017 | Requires a CPU with AVX; on Debian, packages exist for amd64 only. The card explains when the server cannot run it. | | Redis | 6379 | Per-account namespaces with their own password. Also used by the WordPress object cache and Laravel deploys. | Every engine costs memory, so install only what your customers use. ## Create a database 1. Click **New database**. 2. If external servers are configured, choose where to **Create on**: **This server** or an external server. 3. Pick the engine. 4. Enter the **Database name**. It is always prefixed with the account name (`alice_` + `shop` = `alice_shop`): lowercase letters, digits and underscore, 32 characters at most in total. The database user has the same name. 5. Enter or generate a **Password** (8 to 128 characters). For Redis, a strong password is generated automatically. 6. Optionally **Link to website**, so the database appears on that website's **Overview** tab and can be deleted together with it. 7. Click **Create** and store the password: it is not shown again. The **Database credentials** dialog shows the host (`127.0.0.1`), name, user and a connection string: ```text mysql://alice_shop:@127.0.0.1:3306/alice_shop postgresql://alice_app:@127.0.0.1:5432/alice_app mongodb://alice_data:@127.0.0.1:27017/alice_data redis://alice_cache:@127.0.0.1:6379 (key prefix "alice_cache:") ``` For Redis, your keys must start with the namespace prefix (`alice_cache:`); other keys and administrative commands are denied. The number of databases is limited by the account's package. On the Free license, the whole server can have 10 databases. Use **Reset password** to set a new password (applications using the old one stop working until you update them) and **Delete** to remove the database and all its data. ## Remote access By default, databases accept connections from the server itself only. To connect from your office or another server (MariaDB and PostgreSQL): 1. In the database's menu, open **Remote access**. 2. Enter the **Allowed IP addresses / networks**, one per line, for example `198.51.100.7` or `198.51.100.0/24`. 3. Click **Save** and use the **Remote connection string** shown in the dialog. The firewall is opened only for these addresses. Up to 20 entries per database are allowed, and networks may not be larger than /16 (IPv4) or /48 (IPv6); "allow from anywhere" is not possible. Leave the list empty to return to local connections only. ## Export, import and backups - **Export (backup)** writes a dump to `~/backups/db` of the account. - **Import / restore** loads a dump from `~/backups/db` or one you upload (`.sql` or `.sql.gz`; MongoDB uses `.archive.gz`). Tables or collections with the same names are replaced. - Redis data is not exported or backed up. ### Scheduled backups and one-click restore Open **Backups** in a database's menu: 1. Choose an **Automatic backup** schedule: **Off**, **Every hour**, **Every 6 hours** or **Every day**. 2. Set how many backups to **Keep** (1 to 168). 3. Click **Save**, or **Back up now** for an immediate copy. Each backup is listed with its time, type (**manual**, **scheduled** or **before restore**) and size. **Restore** replaces the database with the chosen backup; the current data is backed up first, so a restore can be undone. When the administrator has configured remote S3 backups, copies are also sent to S3. Database backups are also included in the account's full backups (**Backups**). ## Adminer Adminer is a web interface for MariaDB and PostgreSQL databases. Administrators install it from the **Database servers** card or **Components**; it runs on port 8889. Once installed, each MariaDB and PostgreSQL database has an **Adminer** link that opens Adminer with the server, user and database already filled in: enter the database password to continue. Adminer can only be reached by browsers that hold a valid panel session, and its PHP runs as a dedicated user with no access to hosting files. ## External database servers (administrator) Connect a MySQL/MariaDB or PostgreSQL server that runs elsewhere, for example a managed cloud database, so databases can be created there: 1. On the **Databases** page, in **External database servers**, click **Add server**. 2. Enter a name, engine, host, port and an **Admin account** that can create databases and users. 3. Choose the encryption: **TLS required**, **TLS + verify certificate** or **No encryption**. 4. Tick **Customers may use it** to offer it to customers; otherwise it is **admin only**. 5. For MySQL/MariaDB, **Allow connections from any address** controls the database users' host. Off, users accept connections from this server only, so a leaked password is useless anywhere else. 6. Click **Connect & add**. The connection is tested before saving, and the admin password is never shown again. When creating a database on an external server, its password is generated and shown once. Export/import, remote access and automatic backups do not apply to databases on external servers: use your database provider's backups. ### FTP and SFTP Source: https://zopanel.net/docs/ftp-sftp Every hosting account can transfer files with SFTP, using the same username and password as the panel. For developers or designers who should only see one folder, you can add separate FTP accounts on the **FTP / SFTP** page. ## SFTP with the account login SFTP is always available when the account's package allows it (the package's SFTP option, "Allow chrooted SFTP access"). | Setting | Value | | --- | --- | | Protocol | SFTP | | Host | the server's host name or IP | | Port | 22 | | Username | the hosting account name | | Password | the account's password | The session is chrooted to the account's home directory: you see your own `domains/`, `backups/` and other folders, and nothing of other accounts. Websites live in `domains//public_html` (or the website's document root). Files uploaded over SFTP belong to the hosting account, so PHP can read them with the right permissions. If an application reports "permission denied" after an upload with another tool, re-upload over SFTP or fix permissions in the File Manager (files 640, folders 750). Note that **My account → Change password** changes the panel password; ask your provider to change the SFTP password. ### Sign in with an SSH key Keys are safer than passwords and work with FileZilla, WinSCP, rsync over SFTP and similar tools. 1. Open **My account** and find **SFTP & SSH keys**. 2. Paste your public key, one per line, for example: ```text ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA… you@laptop ``` 3. Click **Add key**. 4. Once the key works, turn on **Keys only** to disable password login for this account. Accepted key types are Ed25519, ECDSA (P-256, P-384, P-521), RSA of at least 2048 bits, and security-key types (`sk-ssh-ed25519@openssh.com`, `sk-ecdsa-sha2-nistp256@openssh.com`). The card also shows the **Server fingerprints (check on first connection)**. Compare them with what your client displays the first time you connect, to make sure you are talking to your server. To create a key on your computer: ```bash ssh-keygen -t ed25519 -C "you@laptop" cat ~/.ssh/id_ed25519.pub # paste this line into the panel sftp alice@server.example.com ``` ## FTP accounts FTP is optional. The administrator installs the FTP server (Pure-FTPd with TLS) with **Install FTP server** on the **FTP / SFTP** page, or from **Components**. The installer opens port 21 and the passive port range 30000-30100 in the firewall. ### Create an FTP account 1. On **FTP / SFTP**, click **New FTP account**. 2. Enter the **Username**. It is always prefixed with the account name, for example `alice_designer`. 3. Enter or generate a **Password** (8 to 128 characters). 4. Choose the **Directory** the account is limited to: - `domains/` (all websites); - `domains/` or `domains//public_html` for a single website; - for Git or app websites, `domains//shared` or `domains//source`. 5. Set a **Quota (MB)** if you want to limit how much it can store (`0` = unlimited). 6. Click **Create** and copy the login details. The FTP account cannot leave its directory, and files it uploads belong to the hosting account. The number of FTP accounts is limited by the package (10 by default in new packages). Edit an account to change its password, directory or quota; leave the password empty to keep it. ### Client settings FTP requires TLS (explicit FTPS); plain-text logins are refused. | Setting | Value | | --- | --- | | Protocol | FTP with explicit TLS (FTPES) | | Host | the server's host name or IP | | Port | 21 | | Transfer mode | Passive (ports 30000-30100) | | Username | the full FTP login, for example `alice_designer` | In FileZilla, choose **Require explicit FTP over TLS** as the encryption. FTPS uses the panel's certificate: set **Settings → Panel domain & SSL** so clients see a trusted certificate instead of a warning. Each client IP may open up to 10 connections, and the server accepts up to 100 FTP connections in total. ## Which one should I use? | Need | Use | | --- | --- | | You manage your own websites | SFTP with your account login, ideally with an SSH key and **Keys only** on. | | A freelancer needs one website's files | An FTP account limited to `domains//public_html`, or add them as a **Member** of the website. | | An old tool that only speaks FTP | An FTP account, with TLS. | | Large or many files | SFTP or rsync over SFTP; or upload an archive in the **File Manager** and **Extract** it on the server. | ## Troubleshooting | Symptom | What to check | | --- | --- | | SFTP: "Permission denied" at login | Wrong password, **Keys only** is on, or the package does not include SFTP. | | FTP: login works but listing hangs | Passive ports 30000-30100 must be open on any firewall between you and the server; use passive mode. | | FTP: login refused before the password is checked | The client is using plain FTP: switch it to explicit FTP over TLS. | | Certificate warning in the client | Set a panel domain and issue its certificate in **Settings**. | ## Security & backups ### Security Source: https://zopanel.net/docs/security ZoPanel is built for shared hosting, where you have to assume that one customer's website will eventually be compromised. Several layers keep that problem inside one account: privilege separation in the panel, a sandbox for each account, network filtering, and tools that tell you when something is wrong. This page describes each layer and the settings that control it. ## Architecture: a panel without root ZoPanel runs as two processes with different privileges: | Process | Runs as | Role | | --- | --- | --- | | `zopanel serve` | the unprivileged `zopanel` user, with systemd hardening (`NoNewPrivileges`, `ProtectSystem=strict`, `ProtectHome`, no capabilities) | Web interface, API, database, background jobs | | `zopanel agent` | root | Carries out a fixed list of system actions through a local socket | The web process never has root rights. It can only ask the agent for actions on a fixed allowlist, and the agent validates every parameter again instead of trusting the panel. The agent only accepts connections from the panel user or root. User data is never passed to a shell: every command runs as a fixed program with a separate argument list. Root also avoids touching files inside customers' home directories. File operations in a home directory run as that account's own Linux user, so the kernel enforces the permissions, and a symlink planted by a customer cannot redirect root to another account's files. ## Per-account sandbox Each hosting account is a separate Linux user with its own resources: - **Resource limits:** each account runs in its own systemd slice (`zp-.slice`) with CPU, memory and process limits taken from its package. The PHP-FPM pools, cron jobs and apps of the account all run inside that slice. - **Separate PHP:** each account has its own PHP-FPM pool running as its user, with `open_basedir` and `disable_functions` applied. - **File isolation:** customers cannot read each other's `domains` folders. nginx refuses to follow symlinks that point to files owned by someone else, and SFTP is chrooted to the home directory. - **Disk quotas:** under **Settings → System → Disk quotas**, **Enable quotas** lets the kernel enforce each package's disk space and file count (inode) limits. Without quotas, usage is only measured, and one account can fill the whole disk. - **Hidden processes:** Security Center can mount `/proc` with `hidepid`, so an account only sees its own processes and not other customers' command lines. - **Sandboxed shell:** the browser terminal for customers is limited to their home directory. ### Docker apps Apps from the App Store run in Docker with user-namespace remapping, which ZoPanel turns on automatically. A process running as root or as uid 1000 inside a container is mapped to a host uid range that no hosting account owns, so customers cannot read a container's environment variables or data through a shared uid. ## Firewall and Fail2ban The installer turns on **UFW** with your SSH port and ports 80, 443 and 8888 open, and enables it at boot. You manage rules under **Security → Firewall** (action, port, protocol, source). **Fail2ban** is set up with: | Jail | Watches | Rule | | --- | --- | --- | | `sshd` | SSH logins | 5 failures in 10 minutes, 1 hour ban | | `zopanel` | Panel password and 2FA failures | 8 failures in 15 minutes, 1 hour ban | The mail and FTP services add their own jails when you install them. Banned IPs appear on the **Security** page, where you can unban them. If your websites are behind Cloudflare, turn on **Websites behind Cloudflare** so logs, rate limits and Fail2ban see the real visitor IP. ## Web application firewall **Security → Web application firewall** installs ModSecurity with the OWASP Core Rule Set. It blocks SQL injection, XSS, file inclusion, remote code execution and vulnerability scanners. The firewall is only loaded while at least one website uses it, at about 25 MB of RAM per nginx worker. Each website has three modes in its **Tools** tab: **On (block attacks)**, **Detect only (log)** and **Off**. Start with **Detect only** for a few days, review the events, then switch to blocking. If a rule blocks a legitimate request, click **Allow this** on the event to skip that rule for the website. **Apply to all websites** sets the mode for every site at once. ## Malware scanner and quarantine **Malware scan** looks for webshells, backdoors, obfuscated code, injected JavaScript and PHP files in upload folders. Every account is scanned automatically each Sunday, and customers can run **Scan now** on their own websites. For each finding you can: - **Quarantine**: the file is moved to `~/quarantine` with no permissions, so it can no longer run. The website may break if the file was legitimate. - **Ignore**: mark a false positive. Customers can get an email or Telegram notification when suspicious files are found. ## Two-factor authentication Every user can turn on 2FA under **My account → Two-factor authentication** with any TOTP app (Google Authenticator, Authy, 1Password): 1. Click **Enable 2FA** and scan the QR code. 2. Enter the code and click **Verify & enable**. 3. Save the 10 **recovery codes**. Each one signs you in once if you lose your phone. **New recovery codes** replaces them and the old ones stop working. To make 2FA mandatory, go to **Settings → General → Require two-factor authentication** and choose **Administrators and resellers** or **Everyone**. You must turn on 2FA on your own account first. Users without 2FA must set it up the next time they sign in. If an administrator loses both the phone and the recovery codes, run this on the server as root: ```bash zopanel ctl disable-2fa USERNAME ``` This also signs the user out everywhere and revokes the user's API tokens. ## Panel IP allowlist **Settings → General → Restrict panel access** limits the panel (interface and API) to the IPs and networks you list. Your current IP must be in the list when you save it. If you lock yourself out, run on the server: ```bash zopanel ctl allow-ip --clear # remove the restriction zopanel ctl allow-ip 203.0.113.7 # or allow just one address ``` ## API tokens API tokens (`zpat_…`) are created under **My account → API tokens** and require a Pro license. Creating a token asks for your password (and 2FA code) again. Each token has: - **Permissions:** **Full** acts as your account. **Provisioning only** can only call the provisioning API `/api/v1` (see [Provisioning API](/docs/provisioning-api)). - **Allowed from IPs:** up to 20 IPs or ranges. Empty means any address. - **Expires:** 30, 90 or 365 days, or never. Even a full token cannot reach account security (password, 2FA), other tokens, the terminal, the license, fleet enrolment, panel access and 2FA policy, backup destinations, the recovery key, or restoring the panel configuration. Those actions require a signed-in administrator. Each token is limited to 1200 requests a minute. Changing your password revokes every token you own. ## Activity log integrity and remote syslog The **Activity Log** records security-relevant actions. Each entry is chained to the previous one, so an entry that is changed or deleted breaks the chain. Security Center reports it under **Activity log intact**. An intruder with root access could still erase the whole log. To keep a copy that nobody on the server can change, enter a syslog server under **Settings → General → Send the activity log to**, for example `udp://logs.example.com:514` or `tcp://logs.example.com:514`. ## Secrets encrypted at rest Sensitive values in the panel database are encrypted with AES-256-GCM. This covers S3 and SMTP credentials, bot tokens, API keys, the recovery key, TOTP secrets, external database passwords, app and container environment variables and fleet tokens. The key comes from the server's configuration file, which only root and the panel can read, so a copy of the database by itself reveals none of these values. ## Security Center The **Security** page opens with **Security Center**, which scores the server and the panel and offers a one-click **Fix** where a fix is safe. Its checks include: - **Server:** firewall on and enabled at boot, SSH root password login, SSH password authentication, SSH login attempts, Fail2ban, automatic security updates, pending security updates and reboots, time synchronization, services exposed on public ports, extra UID 0 accounts, empty passwords, process hiding (`hidepid`) and kernel hardening (`sysctl`). - **Panel:** 2FA for all admins, the 2FA policy, no `admin` login name, a valid panel certificate, a panel IP allowlist, off-site backups, a successful backup in the last 48 hours, the panel version, API tokens unused for 90 days, unresolved malware and activity log integrity. SSH fixes check that a key is set up first, so a fix cannot lock you out of the server. ### Backups Source: https://zopanel.net/docs/backups ZoPanel has several kinds of backups that work together. Full account backups give you a complete archive you can move to any ZoPanel server. Incremental snapshots (restic) are small nightly copies you can restore one file at a time. Database backups can run as often as every hour. Every archive can also be copied, encrypted, to S3-compatible storage. This page covers the setup, the safety checks and every way to restore. ## Overview | Kind | Contents | Schedule | Where | | --- | --- | --- | --- | | Full account backup | Websites and databases of one account | Manual, or daily/weekly at 02:00 | `~/backups` of the account, plus S3 | | Incremental snapshot (restic) | Files, databases and mail of every account | Nightly at 01:00 | `/var/backups` on the server, or S3 | | Database backup | One database | Every hour, every 6 hours or daily | `backups/db` of the account, plus S3 | | Customer schedule | Files, databases and mail, as the customer chooses | Daily or weekly, at a chosen hour | The account, and optionally the customer's own S3 | | Configuration backup (`.zpb`) | Panel settings and database | Daily | S3 (see [Disaster recovery](/docs/disaster-recovery)) | ## Full account backups On the **Backups** page, choose an account and click **Create backup**. All websites and databases of the account are archived into `~/backups`. To back up every account automatically, open **Settings → General**: - **Automatic backups:** **Off**, **Daily** or **Weekly (Sunday)**. Backups run at 02:00 server time. - **Backups to keep per account:** 1 to 90 (default 7). Before a backup starts, ZoPanel checks the free disk space. It refuses to start when less than 1 GB, or less than 5% of the disk, is free, so a backup can never fill the disk and take the websites down. Each finished archive gets a SHA-256 checksum, which is checked again before any restore. ## Remote backups to S3 Remote backups need a Pro license. Open **Settings → Remote backups**, turn the switch on and fill in: | Field | Notes | | --- | --- | | **Provider presets** | AWS, Cloudflare R2, Backblaze B2, Wasabi, MinIO and others | | Endpoint, Region, Bucket | From your storage provider | | **Folder prefix** | Defaults to `zopanel` | | Access key, Secret key | Use a key limited to this bucket | | **Remote copies to keep per account** | 1 to 365 (default 14) | | **Path-style URLs (R2, MinIO)** | Required by some providers | | **Plain HTTP (private MinIO only)** | Only for a MinIO server on a private network | Click **Test connection**, then **Save**. From then on, every account backup and database backup is uploaded after it finishes. ### Encryption and the recovery key Copies are encrypted on the server before they are uploaded, with XChaCha20-Poly1305 and a key derived from the panel's **recovery key**. The storage provider, or anyone who gets the bucket's access key, sees only encrypted data. Encrypted files are split into authenticated chunks, so a truncated or modified file fails to open instead of restoring damaged data. To see the key, open **Backups → Disaster recovery → Show key**. **Write it down and keep it off the server**, for example in a password manager. Without it, nothing in the bucket can be read on another server. ## Incremental backups (restic) Incremental backups are set up by the administrator on the **Backups** page, in the **Incremental backups** card: 1. Turn on **Snapshot every account each night (01:00)**. 2. Choose the **Destination**: **This server (/var/backups)**, or S3. S3 uses the bucket from **Settings → Remote backups**, which must be set up first. 3. Set the retention: **Daily** (default 7), **Weekly** (default 4) and **Monthly** (default 6). 4. Click **Show recovery key** and store this key as well. The restic repository is encrypted with it. Each snapshot stores only what changed since the last one, so it is deduplicated, compressed and encrypted. Every Saturday at 05:00, ZoPanel checks the repository structure and reads back 2% of the data. If the check fails, administrators get the **Backup failed** alert. ## Database backups Open **Databases**, then **Backups** for a database: - **Automatic backup:** **Off**, **Every hour**, **Every 6 hours** or **Every day**, with the number of copies to **Keep**. - **Back up now** for a manual copy. When remote backups are on, these copies are also sent to S3, encrypted the same way. ## Customers' own schedules and S3 storage Customers can set up their own schedule on their **Backups** page, in the **Automatic backups** card: - **Schedule**: daily or weekly, with the **Day** and **Time (server)**. - What to include: **Website files**, **Databases**, **Email**. - **Copies on server**: 1 to 30 (default 3). - **Copy to my storage**: any S3-compatible storage (Amazon S3, Cloudflare R2, Backblaze B2, Wasabi, Google Cloud Storage with HMAC keys), with **Copies in storage** from 1 to 365. Customer storage must be a public HTTPS endpoint. Addresses on the server's own network are refused. **Test connection** checks that the bucket can be written. ## Restoring ### A whole account - **From a backup on the server:** **Backups → Restore**. Files and databases are overwritten with the backup's contents. - **From a file made on any ZoPanel server:** **Backups → Restore from file**. The account, websites and databases are recreated with the same names, users and passwords. Websites or databases that already exist with the same names are refused. - **From S3:** **Backups → Disaster recovery → Browse S3**, then open server → account and click **Restore** on a backup. If the account does not exist, it is created. This also works for backups of servers that no longer exist. ### Single files, folders and mail In the **Snapshots (incremental)** card on the **Backups** page, choose a snapshot, browse it and click **Restore** on a file or folder. Files go back in place, and newer files with the same name are replaced. Mail is merged into the mailboxes. Customers can do this themselves for their own account, and they can click **Snapshot now** before a risky change. ### Databases - In a database's **Backups** list, click **Restore**. The current data is backed up first (type **before restore**), so the restore can be undone. - From a snapshot, a restored database is added to that database's backup list, ready to restore. ## Good practice - Turn on remote backups. Security Center warns when **Backups leave the server** is not met, and when no backup has succeeded in 48 hours. - Keep the recovery key and the incremental key outside the server. - Test a restore on a spare server from time to time. A backup that was never restored is not proven. ### Disaster recovery Source: https://zopanel.net/docs/disaster-recovery Account backups bring back websites, databases and mail. To rebuild a whole server you also need what account backups do not hold: the panel's settings, packages, administrator and reseller logins and its keys. ZoPanel keeps these in an encrypted **configuration backup** (`.zpb`). This page explains what is kept where, and the steps for each kind of failure. Test the "server lost" procedure on a spare VPS at least once a quarter. ## What is kept, and where | What | Where | How long | | --- | --- | --- | | Panel database (accounts, sites, DNS, mail, settings) | `/var/lib/zopanel/db-backups/zopanel-YYYYMMDD.db` | 7 days | | Panel database before each update | `/var/lib/zopanel/pre-update/` | Last 3 updates | | Configuration backup (`.zpb`) | S3, under `//_panel/` (daily, when remote backups are on), or downloaded by hand | As many as account backups | | Account backups (files, databases, mailboxes) | `~/backups` on the server, and S3 when remote backups are on | **Settings → General** and **Settings → Remote backups** | | Incremental backups (restic) | Local repository or S3 | Incremental backup retention | | Deleted accounts | `/home/.zp-terminated/-