OpenCode server security: how to run opencode serve and opencode web safely
opencode serve exposes an HTTP API that can read files and run shell commands. How to keep it on localhost, set a password, add TLS and reach it remotely.
- CLI
- 0.2.17
- Preview ref
- a2c45c8
TL;DR
opencode serve and opencode web run an HTTP server whose API can read files, run shell commands, change config and set provider credentials. Treat access to it as shell access to the machine.
A safe setup, in order of importance:
- Keep the default hostname,
127.0.0.1. Only bind to0.0.0.0or use--mdnson a network you control. - Set
OPENCODE_SERVER_PASSWORDwhenever the server is reachable from anything but your own machine. OpenCode's docs say the server is unsecured without it. - Put TLS in front if the traffic crosses a network. Basic auth over plain HTTP sends the password in a form anyone on the path can read.
- Reach it through a private network or a tunnel such as SSH or Tailscale, rather than a public port.
- Keep
--corsnarrow. Add only origins you run yourself.
The problem
OpenCode's client-server design is one of its strengths. The TUI talks to a local server, so you can also drive it from a browser, an IDE plugin, a script or another computer. The docs show how to do that with a single flag:
opencode web --port 4096 --hostname 0.0.0.0That line is fine on a laptop at home. On a café network, a shared office LAN or a cloud VM with an open security group, it publishes a coding agent's control API to everyone who can reach the port. The fix is a handful of settings, and they're easy to skip when the goal is just to get the phone connected.
What the OpenCode server exposes
From OpenCode's server documentation, read on 26 October 2026, the API includes:
| Endpoint | What it does |
|---|---|
POST /session/:id/shell | Run a shell command |
POST /session/:id/message | Send a prompt to the agent |
GET /file/content?path= | Read a file |
GET /find?pattern= | Search file contents |
PATCH /config | Update config |
PUT /auth/:id | Set provider credentials |
POST /mcp | Add an MCP server |
POST /session/:id/permissions/:permissionID | Answer a permission request |
GET /doc | The OpenAPI 3.1 spec |
Anyone who can call these can do what you can do in a terminal on that machine, and can answer the agent's permission prompts as you. That's the threat model for everything below.
The OpenCode server defaults
From the server docs:
| Flag | Default | Notes |
|---|---|---|
--port | 4096 | |
--hostname | 127.0.0.1 | Loopback only |
--mdns | false | When enabled, sets the hostname to 0.0.0.0 and advertises opencode.local |
--cors | none | Extra browser origins to allow |
The defaults are safe on their own. Running opencode without arguments starts the TUI and a server on a random port and hostname, which the TUI uses as its backend. The risk comes from changing the hostname or turning on mDNS.
Step 1: Keep OpenCode on localhost unless you need more
If the only client is on the same machine, don't pass --hostname at all:
opencode serveCheck what's listening:
# Linux
ss -ltnp | grep 4096
# macOS
lsof -nP -iTCP:4096 -sTCP:LISTENYou want 127.0.0.1:4096, not 0.0.0.0:4096 or *:4096.
Step 2: Set a password for the OpenCode server
The server supports HTTP basic auth through environment variables. This applies to both opencode serve and opencode web:
export OPENCODE_SERVER_PASSWORD="$(openssl rand -base64 32)"
opencode serveThe username defaults to opencode. Set OPENCODE_SERVER_USERNAME to change it. The web docs carry a caution: if OPENCODE_SERVER_PASSWORD isn't set, the server is unsecured, which they describe as fine for local use but something to set for network access.
To attach a TUI to a password-protected server:
opencode attach http://10.20.30.40:4096 --password "$OPENCODE_SERVER_PASSWORD"--password defaults to OPENCODE_SERVER_PASSWORD, so if the variable is set in that shell you can leave the flag off.
Keep the password out of shell history and dotfiles in shared repos. A password manager or a file readable only by you, sourced at startup, works.
Step 3: Add TLS in front of the OpenCode server
Basic auth sends the credentials with every request, encoded but not encrypted. Over plain HTTP on a network you don't fully trust, that's equivalent to no password. Options:
-
A reverse proxy such as Caddy or nginx, terminating HTTPS and forwarding to
127.0.0.1:4096. The OpenCode server stays on loopback. -
An SSH tunnel, which encrypts the connection without any new ports:
ssh -N -L 4096:127.0.0.1:4096 you@serverThen point the client at
http://127.0.0.1:4096on your own machine. -
A private network such as Tailscale or WireGuard, which encrypts traffic between your devices. Pair it with the password anyway, because anything else on that network can reach the port.
Cloudflare Tunnel vs Tailscale Funnel vs ngrok compares the tunnel options. Be careful with anything that publishes the port to the public internet. A public URL plus basic auth is a password-only shell.
Step 4: Limit who can reach the OpenCode port
- On a cloud VM, don't open 4096 in the security group. Reach it through SSH or a private network.
- On a home server, keep it off port forwarding on your router.
- With mDNS, remember that
--mdnsbinds to0.0.0.0. It's convenient on a home network and a poor fit for shared Wi-Fi. - With a host firewall, allow the port only from the addresses that need it.
Step 5: Keep CORS narrow
--cors adds browser origins allowed to call the server. Each origin you add is a website whose scripts can talk to your agent from a browser that can reach it. Add only frontends you run, and use the exact origin:
opencode serve --cors https://app.example.comStep 6: Reduce the blast radius on the machine
Even with good access control, the server runs as you. Some habits limit what a mistake can do:
- Run OpenCode as a separate user on a shared server, with access to the repos it needs and no production credentials.
- Keep permission rules strict for shell commands. A remote client answering "allow" is still you allowing it.
- Stop the server when you're done.
opencode serverunning for weeks is a standing exposure.
Reaching OpenCode from your phone without opening a port
The usual reason to bind to 0.0.0.0 is to use OpenCode from another device. That can also be done without the OpenCode server listening on anything but loopback.
If you want to drive OpenCode from your phone without exposing its server, Happier is an open-source app that lets you run and orchestrate Claude Code, Codex, OpenCode and other agents cross-device, from your phone, desktop or the web.
How it handles the OpenCode server it manages:
- Loopback only. Happier starts its managed OpenCode server with
--hostname=127.0.0.1and a free local port, and only reuses a managed server whose address is loopback. - A password for every managed server. If you set
OPENCODE_PASSWORDorOPENCODE_SERVER_PASSWORD, Happier uses yours. Otherwise it generates one for that server and keeps it in an owner-only state file under~/.happier/opencode/managed-servers/. - The password stays on your machine. Happier's docs say it's passed to the server through the environment, never written to Happier's OpenCode server logs and never sent to a remote endpoint.
- Remote access goes through Happier, not through the OpenCode port. The phone, desktop or web app talks to the Happier daemon on your computer through the relay. Session content is end-to-end encrypted by default. Metadata such as session ids and timestamps isn't. You can also self-host the relay.
Permission prompts from OpenCode reach the phone, and you answer them there. happier attach <session-id> reopens the OpenCode TUI for the session on the same machine. Setup is in Run Happier from your phone. Happier is on GitHub, you can download it here, and there's a Discord.
Troubleshooting
Every request returns 401
The client isn't sending the password the server expects. Check that the client's OPENCODE_SERVER_PASSWORD matches the server's, and that the username matches if you changed OPENCODE_SERVER_USERNAME. Some newer OpenCode releases read a different variable name, so check opencode --version against the docs for your release.
The server is reachable from other machines but shouldn't be
Check the listening address with ss -ltnp or lsof. If it says 0.0.0.0 or *, look for --hostname 0.0.0.0 or --mdns in the command or a service file.
The browser blocks requests from a custom frontend
Add the frontend's exact origin with --cors. Don't use a wildcard.
opencode attach can't connect over SSH
Check the tunnel is up and points at 127.0.0.1:4096 on the remote side. Attach to http://127.0.0.1:4096 locally, not to the remote IP.
FAQ
Is opencode serve secure by default?
It listens on 127.0.0.1, so other machines can't reach it. Any process on the same machine can. Without OPENCODE_SERVER_PASSWORD there's no authentication.
Does opencode web use the same password?
Yes. OpenCode's docs say OPENCODE_SERVER_PASSWORD applies to both opencode serve and opencode web.
Can you expose OpenCode to the internet?
You can, but the API runs shell commands. If you must, use TLS, a strong password, and ideally an access layer in front such as a VPN or an identity-aware proxy.
Is the OpenAPI spec endpoint a risk?
/doc describes the API. It doesn't grant access by itself, but it makes the surface easy to explore. It sits behind the same password as the rest.
Get Happier
Happier is an open-source app that lets you run and orchestrate Claude Code, Codex and other agents cross-device, from your phone, desktop or the web.