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.

Based on
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:

  1. Keep the default hostname, 127.0.0.1. Only bind to 0.0.0.0 or use --mdns on a network you control.
  2. Set OPENCODE_SERVER_PASSWORD whenever the server is reachable from anything but your own machine. OpenCode's docs say the server is unsecured without it.
  3. 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.
  4. Reach it through a private network or a tunnel such as SSH or Tailscale, rather than a public port.
  5. Keep --cors narrow. 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.0

That 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:

EndpointWhat it does
POST /session/:id/shellRun a shell command
POST /session/:id/messageSend a prompt to the agent
GET /file/content?path=Read a file
GET /find?pattern=Search file contents
PATCH /configUpdate config
PUT /auth/:idSet provider credentials
POST /mcpAdd an MCP server
POST /session/:id/permissions/:permissionIDAnswer a permission request
GET /docThe 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:

FlagDefaultNotes
--port4096
--hostname127.0.0.1Loopback only
--mdnsfalseWhen enabled, sets the hostname to 0.0.0.0 and advertises opencode.local
--corsnoneExtra 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 serve

Check what's listening:

# Linux
ss -ltnp | grep 4096
# macOS
lsof -nP -iTCP:4096 -sTCP:LISTEN

You 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 serve

The 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@server

    Then point the client at http://127.0.0.1:4096 on 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 --mdns binds to 0.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.com

Step 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 serve running 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.1 and a free local port, and only reuses a managed server whose address is loopback.
  • A password for every managed server. If you set OPENCODE_PASSWORD or OPENCODE_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.

opencodesecurityself-hostingremote-access
Last updated: 2026-10-26