How to self-host SurrealDB on your own VPS without Docker
Run SurrealDB 3.3.0 on your own Ubuntu server with ox and no Docker: one Rust build, a RocksDB data folder and two variables, from the config that ran.
To self-host SurrealDB you need an Ubuntu server, a fork of the SurrealDB repo with one ox.toml file added, and two variables that make the root user. In our run the first deploy took about 69 minutes, most of it compiling SurrealDB's Rust code, and ox set up no other service for it.
How we tested On Oct 17, 2026, ox deployed SurrealDB v3.3.0 from commit 238bfeb1 on a fresh DigitalOcean server with 8 vCPUs and 16 GB of memory. The run then redeployed it, backed it up and rebooted the server. Then a separate test agent, one that did not write the config, signed in as root over the API, created a record, and read it back, and checked the app's services and its backup.
SurrealDB is a database written in Rust. Its README calls it multi-model: document, graph, relational, time-series, geospatial and key-value data in one engine, queried with SurrealQL, a language close to SQL. It can run embedded, as one server or as a cluster. This guide runs one server. Programs reach it over HTTP or WebSocket, and the server has no web pages of its own.
ox deployed SurrealDB v3.3.0 (commit 238bfeb1) on Oct 17, 2026 on a fresh DigitalOcean server with 8 vCPUs and 16 GB of memory; the first deploy took 69 minutes and a redeploy took 66 minutes.
- First deploy: 69 minutes, from adding the project until the app passed the run's first checks.
- Redeploy of the same commit: 66 minutes, until the app passed the checks again.
- Server: DigitalOcean, 8 vCPUs and 16 GB of memory (
s-8vcpu-16gb), with this app alone on it. Every one of the 85 apps we deployed ran on this one size, so it was not picked for this app. - Memory in use after the reboot: 780 MB of 15986 MB, for the whole server: Ubuntu, the app, its services, Caddy and the ox agent. On a server with no apps, ox's tests on real servers hold the agent to 40 MB or less and Caddy to 50 MB or less.
- Source: github.com/surrealdb/surrealdb at v3.3.0, commit
238bfeb1.

The picture is the server's /version answer, the nearest thing to a page.
What you need
An Ubuntu LTS server. The run used the size in the facts above and measured the memory there. The server compiles SurrealDB itself, so it reaches the internet to download Rust crates.
A GitHub account to fork
surrealdb/surrealdb, and an ox account, which you open by signing in with GitHub.A name and a password for the root user. You choose both.
No outside accounts. The SDKs, the Surrealist app, WebSocket connections and a cluster were not tested.
The steps to install SurrealDB on Ubuntu
In the ox dashboard press Add a server and run the command it shows, as root, on your Ubuntu machine. It installs the ox agent and Caddy.
Fork surrealdb/surrealdb and start from the tag v3.3.0, commit 238bfeb1, the release the run deployed. Add the ox.toml shown below at the top of the repo, then commit and push. Next write surrealdb.env on your laptop:
SURREAL_USER=<a name for the root user>
SURREAL_PASS=<a long password>With the ox CLI signed in, run:
ox new <you>/surrealdb
ox review surrealdb --from-file surrealdb.env --waitThe review saves both values and starts the first deploy, and --wait prints the run until it ends. The quickstart shows them in the browser.
When the deploy is green, open the address ox shows. /health answers 200 and /version answers surrealdb-3.3.0. The run used that preview address only. A domain of your own was not part of it, and the domains page covers how ox adds one.
The test agent signed in as root, made a record and read it back through the HTTP API. Fill in your address, user, password and token:
SITE=https://<the address ox shows>
curl -s $SITE/signin -H 'Content-Type: application/json' \
-d '{"user":"<SURREAL_USER>","pass":"<SURREAL_PASS>"}'
curl -s $SITE/sql -H "Authorization: Bearer <token>" \
-d "DEFINE NAMESPACE IF NOT EXISTS demo; USE NS demo; DEFINE DATABASE IF NOT EXISTS demo; USE DB demo; CREATE person:ada CONTENT { name: 'Ada' };"
curl -s $SITE/sql -H "Authorization: Bearer <token>" \
-d "USE NS demo DB demo; SELECT * FROM person:ada;"The sign-in answer holds a token. Each /sql answer has one entry per statement, and the read returns {"id":"person:ada","name":"Ada"}. A call with no token gets 403 and a bad token gets 401. Check each statement's status: a call can answer 200 while one statement in it says ERR.
The ox.toml, line by line
# SurrealDB as one server process, the way its docs start it in production:
# `surreal start` with a RocksDB file store and a root user made from
# SURREAL_USER and SURREAL_PASS on the first start.
# libclang for bindgen (surrealdb-librocksdb-sys, rquickjs-sys); cmake for
# the C libraries a -sys crate builds with it (aws-lc-sys). ox adds the C
# compilers for a Rust app, and libssl-dev with pkg-config for openssl-sys.
packages = ["clang", "libclang-dev", "cmake"]
# The first start makes the root user from these (the docs' --user and
# --pass); without them the server has no user at all and every query is
# refused.
required_variables = ["SURREAL_USER", "SURREAL_PASS"]
[app]
# Binds loopback only, so the proxy is the one way in, and reads the
# visitor's address from the proxy's X-Forwarded-For. The data path is
# relative: data/ in the release is the kept folder.
start = "exec .ox/bin/surreal start --no-banner --bind 127.0.0.1:$PORT --client-ip X-Forwarded-For rocksdb://data/surreal.db"
health = "/health"
# RocksDB holds an exclusive lock on its folder: a second process on the
# same data cannot open it, so the old one stops first.
stop_before_start = true
[build]
commands = [
# The default features: the storage engines, the HTTP server, GraphQL,
# scripting. The release binary's extra `ml` feature downloads ONNX
# Runtime at build time, and `cjk` embeds Chinese, Japanese and Korean
# dictionaries for full-text search; neither is needed to store and
# query data.
"cargo build --release --locked",
# Keep only the binary: the multi-GB target folder would otherwise stay
# in each of the releases ox keeps.
"mkdir -p .ox/bin && cp target/release/surreal .ox/bin/surreal && rm -rf target && .ox/bin/surreal version",
]
[storage]
# The Docker image's VOLUME /data: the RocksDB files.
keep = ["data"]packagesaddsclang,libclang-devandcmake. Two crates,surrealdb-librocksdb-sysandrquickjs-sys, run bindgen, which needs libclang, and cmake builds the C code of crates such asaws-lc-sys. ox adds the C compilers, andlibssl-devwithpkg-configforopenssl-sys.required_variablesmakes ox ask for both root values before the first deploy. Without them the server has no user and refuses every query.startruns the binary withexec, so ox's stop signal reaches SurrealDB and not a shell.--bind 127.0.0.1:$PORTlistens on loopback only, on the port ox hands out, so Caddy is the one way in.--client-ip X-Forwarded-Forreads the visitor's address from the proxy's header, since the socket address behind a proxy is always the proxy's.--no-bannerleaves out the startup logo.The last argument,
rocksdb://data/surreal.db, picks RocksDB for storage. The default build also holds in-memory, SurrealKV and TiKV storage. The path is relative, so the files land indata, the folder kept across deploys.healthis SurrealDB's/healthroute, which runs a check on the datastore. ox switches traffic only after it answers.stop_before_startis on because RocksDB locks its folder, and a second process cannot open the same data. A deploy stops the running copy before it starts the new one, so a redeploy has a short gap where the site answers 502. The first deploy has none.The first build command is
cargo build --release --locked: the exact versions inCargo.lockand the default features, which include the storage engines, the HTTP server, GraphQL and scripting. The featuresmlandcjk(Chinese, Japanese and Korean dictionaries for full-text search) are off by default and stay off. SurrealDB's release profile setslto = trueandcodegen-units = 1, which trades build time for an optimized binary.The second command copies the binary to
.ox/bin/surreal, deletes thetargetfolder and prints the version, so the deploy fails at once if the binary does not run. ox keeps older releases for rollback, and each would otherwise carry the very large build folder. A redeploy compiles from the start again, and the run's took about as long as the first deploy.[storage]keepsdata: ox links the project's data folder into each new release, so the RocksDB files survive deploys and go into backups.
There is no [tools] table: ox reads the repo's rust-toolchain.toml, which asks for Rust 1.95. There are no workers or cron jobs. SurrealDB runs its housekeeping, such as index compaction and changefeed clean-up, inside the one process, the only unit the run's check found.
SURREAL_USER
SURREAL_PASSSURREAL_USER and SURREAL_PASS are the name and password of the root user, the account that made the namespace and database in the run. surreal start reads both from its environment, so the start line carries no password. They count only while no root user exists: on a later start with other values, SurrealDB logs that it found existing root users and keeps the first. See variables for how ox stores values.
Updates and backups
To move to a new release, merge its tag into your fork and push, or run ox deploy surrealdb --wait. The deploy compiles the new release again. The repo's doc/UPGRADING.md says that from 3.3.0 on, a build that lacks a data migration the database has recorded refuses to start, so read it before a rollback to an older release. The run redeployed the same commit, so a jump between versions and a rollback were not tested.
Backups need no setup. The kept data folder is its own entry on the Backups tab, and ox backs it up each day, copying it while SurrealDB runs. The run's backup came from Back up now: a tar.gz of data that held the RocksDB files. List them with ox backups surrealdb. A restore was not tested. The backups page covers copies in an S3 bucket.
Frequently asked questions
Do I need Docker to self-host SurrealDB?
No. SurrealDB publishes a Docker image, but the server is one Rust binary. ox compiles it on your server from your fork and runs it as a system service, and sets up no other service. A redeploy compiles it again, so an update takes about as long as the first deploy.
How do I create the first SurrealDB root user?
Set SURREAL_USER and SURREAL_PASS before the first deploy. SurrealDB makes the root user from them on its first start, and you sign in at /signin with the same two values. Later starts keep that first root user and ignore new values, so changing the variables does not change the password.
How much memory does self-hosted SurrealDB use?
After the reboot, the run's whole server used 780 MB of its 16 GB. That counts Ubuntu, SurrealDB, Caddy and the ox agent. The build's memory was not measured. The rest depends on your data and what runs beside it. ox works on any server from 1 GB up and says so when memory is why an app stopped.
Does SurrealDB come with a web interface?
No. The server has no pages of its own, and its root address redirects to Surrealist, the app SurrealDB hosts at surrealdb.com. The test agent used the HTTP API instead, and connecting Surrealist to the server was not tested. The /version route answers in plain text.
Is SurrealDB free to self-host?
SurrealDB uses the Business Source License 1.1, which its LICENSE file calls not an open source license. Its Additional Use Grant bars using it as a Database Service, meaning database functionality for third parties that lets them create, manage or control schemas or tables. The file's change date is 2030-01-01, when it turns to Apache 2.0.
New to running apps on your own server? How ox self-hosts your apps shows what ox sets up on your server.
