Self-hosting
Supported operating systems
Section titled “Supported operating systems”| Distro | Versions | Container runtime |
|---|---|---|
| Ubuntu | 20.04+ | Docker (auto-installed) or Podman |
| Debian | 11+ | Docker (auto-installed) or Podman |
| Fedora | 38+ | Docker or Podman (auto-installed) |
| RHEL / Rocky / AlmaLinux | 8+ | Docker or Podman (auto-installed) |
| CentOS Stream | 9+ | Docker or Podman (auto-installed) |
| openSUSE Leap / Tumbleweed | latest | Docker or Podman (auto-installed) |
| Arch Linux | rolling | Docker or Podman (auto-installed) |
Requirements: systemd, x86_64 or arm64, kernel ≥ 5.4. Alpine and non-systemd distros are not supported.
Prerequisites
Section titled “Prerequisites”- A supported Linux distro (see above)
- At least 5 GB free disk space (images + k3s + data)
- A domain you control, with DNS records pointing at this server. Which records depends on the DNS mode; see DNS setup
- Ports 80 and 443 open in your firewall and not in use by other services on the host, plus 53 (TCP+UDP) in the default NS-delegation mode. Port 53 conflicts with
systemd-resolvedon Ubuntu 22.04+; the installer will warn you. UDP 41641 is optional: it lets nodes reach the gateway directly instead of through a relay - A host or provider firewall keeping everything else closed. The API (4000), the built-in registry (5000), the k3s API (6443), node_exporter (9100) and the kubelet (10250) listen on every interface so the mesh can reach them, and the registry has no authentication of its own. The dashboard warns you when the installer found no host firewall
- Root / sudo access
Install
Section titled “Install”Two ways to do the same install. Run it yourself, or hand it to an agent - the agent runs the same script and stops at the same questions.
Run it yourself
Section titled “Run it yourself”sudo bash -c "$(curl -fsSL https://meshploy.com/install.sh)"The script installs Docker (if needed), downloads Meshploy to /opt/meshploy, walks you through an interactive setup (domain, IP, secrets), and starts the full stack. Select Master for the gateway node or Worker to join an existing mesh.
Before the first question, the installer offers to continue in a browser instead. Say yes and it prints a setup token and an address on port 9000, where the same setup runs as a web page that checks your DNS as you go and streams the install. Port 9000 has to be reachable from wherever your browser is.
Hand it to an agent
Section titled “Hand it to an agent”If you work through Claude Code, Codex or a similar agent with a terminal, give it the prompt below instead of running the command yourself. It carries no secret, so it is safe to copy anywhere.
The agent stops where the decisions are yours: it will not choose your domain, and it does not need your DNS provider’s password.
# Install Meshploy on my server
Set up a Meshploy gateway on a Linux server I own, and leave me at the setuppage with the install running.
## Work out how to reach the machine
119 collapsed lines
Look before you ask. Read my SSH config for hosts I have already set up:
```grep -iE '^host ' ~/.ssh/config 2>/dev/null```
Then ask me one question, not four:
- If that listed candidates, show them and ask which one this is - or whether it is a machine not in there.- If it listed nothing, or I say it is a new machine, ask for the address. The user is **root** unless I say otherwise; only ask about the port and key if the defaults do not work.
Confirm it works **without anything being typed**, and stop if it does not:
```ssh -o BatchMode=yes -o ConnectTimeout=8 <host> 'hostname; id -u'```
`BatchMode=yes` is not optional. Without it, a server that wants a passwordleaves this command waiting for input you cannot give, and it looks to me likeyou are still working.
If it fails, do **not** ask me for a password, and do **not** use `sshpass`:
- **SSH wants a password.** I run `ssh-copy-id <host>` once, and you carry on.- **It cannot connect at all.** Give me the error; the address or the port is probably wrong.
If it answered but `id -u` was not `0`, you are not root. Check whether thataccount can become root without typing anything:
```ssh -o BatchMode=yes <host> 'sudo -n true' && echo "sudo ok"```
If that fails, stop and tell me - I will either give you a root login or runthe install myself and tell you when the setup page is up.
Use whatever I gave you as `<host>` from here on - an alias from my config ifthat is what it was, and `user@address` if not.
## Check the machine is one we can use
```ssh <host> 'cat /etc/os-release | head -2; systemctl --version | head -1; df -h / | tail -1; ls /opt/meshploy 2>/dev/null'```
- Ubuntu 20.04+, Debian 11+, Fedora 38+, RHEL/Rocky/Alma 8+, CentOS Stream 9+, openSUSE, or Arch. **Alpine and anything without systemd will not work** - stop and tell me.- At least 5 GB free on `/`.- If `/opt/meshploy` already exists, this server already has Meshploy. Stop and ask me whether I want to reinstall or upgrade instead.
Also check nothing already holds the ports Meshploy needs:
```ssh <host> 'ss -Hltnp "sport = :80" ; ss -Hltnp "sport = :443" ; ss -Hltnp "sport = :53"'```
If something does, tell me what it is and stop. Do not stop or remove it.
## Get my approval
Show me this and wait for a yes:
```Server: <host>Installs: Docker (if missing), k3s, Headscale, CoreDNS, Caddy, MeshployOpens: 80, 443, and 53 unless I choose self-managed DNSNeeds: root (sudo), a domain I control, and about 10 minutes```
Ask me which domain I will use, and tell me I will need to add DNS records forit partway through. Do not pick a domain for me.
## Run it
```ssh -t <host> 'bash -c "$(curl -fsSL https://meshploy.com/install.sh)"'```
As root that is all it needs. On a non-root login that could become root above,put `sudo` in front of `bash`.
When it offers to continue in a browser, say **yes**. It prints a setup tokenand an address on port 9000.
## Open the setup page for me
Port 9000 is usually closed to the internet, so tunnel it rather than asking meto open a firewall:
```ssh -N -L 9000:127.0.0.1:9000 <host>```
Leave that running, open `http://localhost:9000` in my browser, and give me thesetup token. Then stop and let me take over: the domain, the DNS records andthe owner account are mine to enter.
## If it fails
- Port 53 is taken on Ubuntu: that is `systemd-resolved`. Tell me, and mention that `--dns-mode=ondemand` avoids needing port 53 at all. Do not disable it yourself.- Unsupported distro, too little disk, or Meshploy already installed: stop and tell me. Do not work around it.- Anything else: show me the output and stop. Do not re-run the installer.
## Rules
- Do not choose or register a domain for me.- Do not change firewall rules, DNS records, or anything else on the server.- Do not read or print my environment files.
Docs: https://docs.meshploy.com/self-hosting/Adding a worker later needs no prompt of this size: the console’s Cluster → Add a node gives you a one-line command carrying a single-use token, and a matching prompt beside it.
DNS setup
Section titled “DNS setup”Pick a domain for Meshploy, ideally a dedicated subdomain such as meshploy.example.com. The console, the API and every app you deploy get hostnames under it. There are two ways to point it at the server; the installer asks which one when it cannot see a delegation already in place.
NS delegation (default). The gateway runs its own authoritative DNS (CoreDNS) for the domain, which lets it obtain one wildcard certificate. Add two records where the parent zone is hosted (for meshploy.example.com, that is example.com), the A record first:
ns1.meshploy.example.com A <gateway-public-ip>meshploy.example.com NS ns1.meshploy.example.comAn NS record names a nameserver rather than an address, which is why the ns1 A record has to exist for the delegation to resolve. Verify once it has propagated:
dig @<gateway-public-ip> console.meshploy.example.com ASelf-managed DNS (--dns-mode=ondemand). For providers that cannot delegate a subdomain (Hostinger, for example). Keep DNS with your provider and add two A records; Caddy then issues a certificate per hostname the first time each one is requested:
*.meshploy.example.com A <gateway-public-ip>meshploy.example.com A <gateway-public-ip>Some providers call the second one the root or @ record. Verify:
dig +short console.meshploy.example.com ARe-running the installer keeps whichever mode the server was installed with.
Creating the owner account
Section titled “Creating the owner account”When the install finishes, open https://console.<your-domain> and register. The first account owns the instance, so the form asks for the one-time setup token the installer printed. If you no longer have it, print it again on the gateway:
sudo meshploy setup-token showThe token is only accepted until that first account exists. If someone else may have seen it before then, issue a new one with sudo meshploy setup-token rotate and restart the API (cd /opt/meshploy && docker compose up -d api).
Managing your installation
Section titled “Managing your installation”Installing, reinstalling and removing all go through the install script:
| Command | What it does |
|---|---|
sudo bash -c "$(curl -fsSL URL)" | Fresh install |
sudo bash -c "$(curl -fsSL URL)" _ --reinstall | Update images and config, preserve database and TLS certs |
sudo bash -c "$(curl -fsSL URL)" _ --reinstall --wipe-data | Full reinstall from scratch, wipes database and TLS cert cache |
sudo bash -c "$(curl -fsSL URL)" _ --uninstall | Remove Meshploy (interactive) |
sudo bash -c "$(curl -fsSL URL)" _ --cli-only | Install or update the meshploy CLI binary only — safe on existing nodes |
sudo bash -c "$(curl -fsSL URL)" _ --dns-mode=ondemand | Install without NS delegation — you add a wildcard A record and Caddy issues a certificate per hostname |
sudo bash -c "$(curl -fsSL URL)" _ --edge | Install edge builds from main instead of the latest stable release |
Replace
URLwithhttps://meshploy.com/install.sh
Release channel: by default the installer tracks the latest stable release:
:latestimages and thedeploy/config from the newest release tag. Pass--edgeto trackmaininstead, which pulls:mainimages and branch config. Edge carries work that has not been released yet, so prefer stable unless you need something that has just landed. The choice is not remembered: a later re-run orsudo meshploy server-upgradewithout--edgewritesMESHPLOY_CHANNEL=latestand moves the install back to stable, so pass--edgeevery time if you mean to stay on it. Upgrades from the console keep the channel the server is on. To change it there, the server’s owner uses Settings → Server, which shows both channels and where the server sits on them. The console only switches forward: edge to stable becomes available once a release includes the build the server runs, so it never installs older code.
TLS cert cache: Caddy stores Let’s Encrypt certificates in a Docker volume.
--reinstallalways preserves this volume to avoid hitting rate limits (5 certs per domain per week). Use--wipe-dataonly when you genuinely need a clean slate.
Upgrading:
sudo meshploy server-upgradeon the gateway downloads the configuration and images for the latest release, then installs them and restarts the services, keeping your data. If the services do not come back, it puts the previous version back. The server’s owner can also upgrade from the console: Update available in the sidebar opens an Upgrade now button. New installs are set up for that; on an older server, runsudo meshploy updater startonce after upgrading. Add--edgeto upgrade tomain. Runsudo meshploy updatefirst, so the upgrade runs with the latest CLI. See the CLI reference.
Enterprise
Section titled “Enterprise”Community is complete and stays free: everything above is Apache-2.0, with one organization per server. Enterprise adds organizational features under a licence (the console’s Settings → Licence → Compare editions lists them) and runs as a separate pair of private images, ghcr.io/meshploy/api-ee and ghcr.io/meshploy/web-ee, built from the same source and version as each Community release.
Moving a server to Enterprise keeps its data and settings:
- Get a licence at meshploy.com/enterprise. It is bound to your domain and names the image it grants.
- Activate it on the Community server: an admin pastes it into Settings → Licence, or runs
meshploy license activate <token>. It is verified and stored, and grants nothing until the switch, since Community has no Enterprise features built in. - Give the gateway registry access once, as root, with a GitHub token that can read packages, for the account the licence was granted to:
echo <token> | sudo docker login ghcr.io -u <github-user> --password-stdin(orpodman). Credentials never pass through the console. - Switch. The server’s owner presses Switch to Enterprise in the licence section, which runs through the same updater as an upgrade, or runs
sudo meshploy server-upgrade --eeon the gateway. Either points the API and the console at the Enterprise images and restarts them; if the images cannot be pulled, nothing changes.
Upgrades then work as before, on either channel. The Enterprise images for a release appear a few minutes after it, and an upgrade started before they do stops without changing anything. meshploy license status shows the edition and the licence.
To go back to Community, remove MESHPLOY_API_IMAGE and MESHPLOY_WEB_IMAGE from /opt/meshploy/.env and run sudo meshploy server-upgrade. The licence and any Enterprise data stay in the database, unused.
Local Development
Section titled “Local Development”Prerequisites
Section titled “Prerequisites”- Go 1.25+
- Node.js 20+
- PostgreSQL 15+
- Docker
1. Clone and configure
Section titled “1. Clone and configure”git clone https://github.com/meshploy/meshploycd meshploycp .env.example .envEdit .env:
DATABASE_URL=postgres://user:password@localhost:5432/meshploy?sslmode=disableJWT_SECRET=your-long-random-secretENCRYPTION_KEY=0123456789abcdef0123456789abcdef # exactly 32 characters: openssl rand -hex 162. Start PostgreSQL
Section titled “2. Start PostgreSQL”docker compose -f deploy/docker-compose.dev.yml up -dThis starts only PostgreSQL on port 5432. Headscale, CoreDNS, Caddy, and the registry are not needed for local development — the API and frontend work without them.
3. Run the API
Section titled “3. Run the API”cd apps/api && go run main.goAPI at http://localhost:4000. The OpenAPI spec is at /openapi.json; it needs a bearer token, like every non-public route.
4. Run the Proxy
Section titled “4. Run the Proxy”cd apps/proxy && go run main.go5. Run the Web dashboard
Section titled “5. Run the Web dashboard”cd apps/web && npm install && npm run devDashboard at http://localhost:5173