# Security: how ox hardens your VPS and isolates apps How ox secures your VPS: SSH keys only, a firewall, fail2ban, each app as its own sandboxed user, signed updates, and an audit you can run any time. When you add a server, ox locks down the host before it enrolls, runs each app as its own user in a sandbox, and takes only updates signed by ox's release key. This page lists what ox sets, how to check it on your server, and what stays your job. ## What ox sets on your server - **SSH:** keys only, no root login, no passwords even when the cloud image turns them on, and weak MACs off. ox logs in again through the new settings before it keeps them, so a mistake cannot lock you out. - **Firewall:** ufw refuses incoming connections by default and opens SSH, 80 and 443. Rules already in ufw before ox, such as a port you opened yourself, stay open. - **Password guessing:** fail2ban bans addresses that keep failing SSH logins. - **Updates:** Ubuntu's security updates install themselves every day. - **The kernel:** io_uring is off for every process. This part of the kernel has a long record of privilege bugs, and no app ox runs needs it. ## How your apps are kept apart Each project runs as a system user of its own, in a systemd unit with no extra privileges. An app cannot see other users' processes, cannot load kernel modules or trace other processes, and can write only its own directories. Its ports are reachable only by its own project and Caddy, and it cannot read the cloud's metadata service, where a droplet keeps its setup data. A process set to `sandbox = "relaxed"` in [ox.toml](https://deploywithox.com/docs/config) keeps its own user, no extra privileges, the hidden processes and the kernel limits, but loses the read-only system and the system call filter: it can write wherever its user may and make any call the kernel allows an ordinary user. Builds run as the same user with the same limits on processes and the kernel. Your [variables and secrets](https://deploywithox.com/docs/operate/variables) sit in files only root and that project can read. ## Signed updates The agent on your server updates only to a build that carries two signatures: one from the ox Cloud plane and one from ox's release key, which the ox team keeps on its own machine with one backup in a password manager, never on the plane or a server. So someone who took over the plane could not replace the ox program on a server already added with one of their own. They could still run jobs on your server as your projects' users and read your projects' variables, which the plane does to deploy, and a server added while they held the plane would get whatever install script they served. The agent talks to the plane only over HTTPS, and only outward: the plane never connects in. ## Check your server yourself Before a server enrolls, ox reads back what it set and refuses to go on if any row fails. Run the same check on an enrolled server at any time; it changes nothing: Security self-audit: ```sh sudo ox install --audit ``` The rows `sudo ox install --audit` prints on a server ox set up: each check, what it must be, and what the host has. The same table runs before a server enrolls, and any failed row stops the install. It prints a pass or fail row for SSH root and password login, the firewall, fail2ban, automatic updates and the modes of ox's secret files. On a server added before these settings existed, first press **Update** on its page: an older ox has no `--harden`. Then `sudo ox install --harden` turns io_uring off, writes the rules that keep apps apart and away from the metadata service, and runs the audit. It leaves SSH, the firewall, fail2ban and updates as they are. For the newer SSH settings, copy `scripts/bootstrap.sh` and `scripts/security-rollout.sh` from the ox repository to the server and run `sudo bash security-rollout.sh --server`: it writes them, runs `ox install --harden` too, and puts the saved files back after five minutes unless you log in again from outside and run the `--confirm` command it prints. ## Where your data lives ox keeps only what it needs to run your account. Your app and its data stay on your server. These stay there: - Your code, builds and releases. - Your variables and secrets. ox shows them to you but keeps no copy. - Your databases, files and uploads. - Backups of your services. A copy goes to your own S3 bucket only if you add one. - Your app's logs. ox reads them only when you ask to see them. Logs you open, rows you browse in Explore, and backups you download pass through ox to your screen. ox does not save them. The [FAQ](https://deploywithox.com/#faq-data) lists what ox keeps and for how long, and the [privacy policy](https://deploywithox.com/privacy) has every detail. ## What stays your job - **Your app's own code and dependencies.** The sandbox limits what a bad dependency can reach; it does not stop it from reading that project's own data. - **Who holds an SSH key.** Anyone with a key for the operator account is root on the server. - **The Add server command.** Treat it like a password until the server shows up; it expires on its own. - **Backups somewhere else.** Keep copies in your own S3 bucket, as [Backups and restore](https://deploywithox.com/docs/operate/backups) explains. Something here looks wrong, or you found a hole? Write to the address on the [privacy page](https://deploywithox.com/privacy). If a deploy fails after a hardening change, start with [Troubleshooting](https://deploywithox.com/docs/troubleshooting), and see the [CLI reference](https://deploywithox.com/docs/cli) for the rest of `ox`. Last updated 2026-10-09. The page as HTML: https://deploywithox.com/docs/security