opencode-web on a VPS: an AI assistant in your pocket

· 14 min read

I use AI assistants every day — for code, architectural decisions, finding information. But there’s a problem: every tool is tied to a terminal or an IDE. The moment I step away from my laptop, the AI stays behind. In a cafe, on the road, from my phone — there’s no access.

You could buy a SaaS subscription: Claude Code in the cloud, Codex through the mobile app. But then my code, my MCP servers, and my config live on someone else’s infrastructure. My API key even more so.

The solution: run opencode-web on your own VPS. One server, three interfaces: Web UI for the phone, TUI for the laptop, REST API for automation. Your key, your settings, full control.

This article is my personal experience: how I picked a VPS, set up security, wired up Yandex ID authentication, and what came out of it.

Which AI agents have web access

Before spinning up your own server, it’s worth asking — are there alternatives at all? I compared popular AI agents by whether they offer remote access through a browser or phone.

AgentWeb accessHow it worksWhat’s under the hood
Claude Code✅ Remote Controlclaude remote-control → QR code → claude.ai/code or the Claude mobile appA local CLI session registers with Anthropic’s servers. The browser/phone is a window into that session. No ports are opened.
Codex CLI⚠️ Via mobile app + third-party web UIsThe ChatGPT mobile app connects to the Codex App on macOS/Windows. Or npx rmtcdx / remcodex — third-party web interfacesOfficially: remote control through an OpenAI account. Unofficially: rmtcdx with Tailscale support, codex-remote-hub on ttyd+tmux
OpenCode✅ Native opencode webOne command — and an HTTP server with Web UI and REST API is up. Any browser, any phone.Everything lives on your server. No binding to the vendor’s cloud. Basic auth or an OAuth proxy.
AiderNoneTerminal, local only
ClineNoneVS Code extension, local only
CursorNoneA VS Code fork, local only

The key difference with opencode-web: it doesn’t “mirror” a local session through someone else’s server, like Claude Code’s Remote Control does. It is the server. You decide which hardware it runs on, which model it uses, and who gets access.

One more important nuance: opencode-web doesn’t only bring up the Web UI, it also brings up the REST API — the same one that opencode attach and opencode run talk to. One backend serves the browser, the terminal, and automation.

Claude Code and Codex also have APIs, but they’re either tied to the cloud or require separate solutions. With OpenCode — it’s out of the box.

Sources:

What VPS options are there and what it costs

opencode-web doesn’t need powerful hardware. Minimum requirements are 1 vCPU and 1 GB RAM. Comfortable is 2 vCPU and 2 GB. Here are the current market options I collected:

ProviderConfigurationPriceNotes
Oracle Cloud Free4 ARM / 24 GB€0Registration is painful, may kill idle machines
Hetzner CX222 x86 / 4 GB€3.79/moBest price/performance in Europe, x86
Netcup VPS 1000 G112 x86 / 4 GB€3.99/mo128 GB SSD — 3× more disk
RackNerd (sale)1 vCPU / 1 GB~$1/moBare minimum, US only
Yandex Cloud2 vCPU / 2 GB~€17/mo3–5× more expensive, but a Russian IP

I went with Yandex Cloud. Configuration: 2 vCPU, 8 GB RAM, 40 GB disk — 4,800 ₽/mo. That’s with a wide margin: average CPU utilization is 20%, memory isn’t hitting the ceiling either. You can take the minimum configuration (2 vCPU / 2 GB) — it’ll be significantly cheaper. I took the headroom because the server handles other tasks too.

An important point: the code lives in a git repository. On the server there’s only the working copy that opencode clones and edits. So disk size isn’t critical, 20 GB is plenty.

Security: so it doesn’t hurt later

Why secure the server at all

opencode-web isn’t a static site or a blog. It’s an agent with rights to the filesystem, the network, and command execution. If someone gets access to it, they can:

Plus the objective reality: any public IP gets scanned by bots within minutes of being created. SSH brute force, open port scanning, hunting for vulnerable services — that’s the background noise of the internet, not a targeted attack. The protection isn’t against a professional hacker coming specifically for you, it’s against automated scanners knocking on every door in a row.

A normal web service is moderate risk. An AI agent with filesystem rights and API keys is high risk. So the protection here is in three layers, not a single password.

Three layers of defense

LayerWhat it protectsTool
NetworkAccess to port 4096 from the internetTailscale + ufw
ApplicationAccess to the Web UIOAuth via Yandex ID
OSSSH accessKeys instead of passwords, fail2ban, auto-updates

The idea is simple: even if one layer is breached, the next ones hold the attack back. An attacker has to get through Tailscale (without knowing your devices), then through OAuth (without having your Yandex account), and only then reaches opencode-web.

The defense chain, top to bottom:

1. Internet
2. Tailscale — only your devices
3. ufw — only traffic from tailscale0
4. OAuth — only you (Yandex ID)
5. opencode-web :4096
6. SSH — keys + fail2ban

Tailscale: a private network instead of open ports

The first rule: don’t expose ports to the outside. No 0.0.0.0:4096 to the internet. Use Tailscale instead.

Tailscale is a mesh VPN built on WireGuard. Free up to 100 devices. You install the client on the VPS and on your phone — and they end up in the same private network. After that, opencode-web listens on 0.0.0.0, but the firewall (ufw) only lets traffic through from the tailscale0 interface:

Terminal window
ufw allow in on tailscale0
ufw --force enable

Now the server is only visible to your devices on the Tailscale network. Phone, laptop, tablet — they connect via the internal IP of the form 100.x.x.x:4096.

Setting up Tailscale on the server is one command:

Terminal window
curl -fsSL https://tailscale.com/install.sh | sh
tailscale up

After that you open the link in a browser, authorize — and the server is on the network. On the phone you install the Tailscale app (App Store / Play Store) and log in with the same account.

Authentication via Yandex ID

OpenCode Web itself supports HTTP basic auth. But for a personal server you want proper authentication — without memorizing yet another password and with two-factor auth out of the box.

Important: if you use Tailscale and trust every device on your network, authentication isn’t mandatory. The private network itself already cuts off the outside world. But I decided to add OAuth as an extra layer.

The scheme ended up like this:

Browser
↓ Tailscale
Caddy :443 (reverse proxy, HTTPS)
auth-proxy (JS, 165 lines)
↓ token check
├─ Yandex ID OAuth
└─ opencode-web :4096

Caddy works as a reverse proxy with automatic HTTPS (Let’s Encrypt). I pointed a second-level domain at the server — that gives a proper certificate and a human-readable URL instead of http://100.x.x.x:4096.

Auth proxy is a thin JavaScript shim, 165 lines of code. I asked OpenCode to write it. It does exactly one thing: checks the Yandex ID OAuth token before letting a request through to opencode-web. No logic other than authentication.

The algorithm:

  1. Register an OAuth application in Yandex ID — you get a client_id and client_secret
  2. The callback URL points at your server — after login, Yandex redirects back with the token
  3. The auth proxy verifies the token via Yandex’s API and either lets you through to opencode-web or returns 401

Why Yandex ID:

Let me repeat: you can do without OAuth. Tailscale already gives sufficient isolation. But if you want a pretty URL with a domain and proper HTTPS — Caddy + auth proxy solve that.

SSH hardening and fail2ban

A few mandatory settings on the VPS itself:

Terminal window
# Keys only, no passwords, no root login
sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
sed -i 's/^#*PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
sed -i 's/^#*PubkeyAuthentication.*/PubkeyAuthentication yes/' /etc/ssh/sshd_config
systemctl restart sshd

Plus fail2ban to protect against SSH brute force:

Terminal window
apt install -y fail2ban
systemctl enable --now fail2ban

And automatic security updates, so you don’t miss critical patches:

Terminal window
apt install -y unattended-upgrades

Core setup concepts

I won’t publish a full setup script — it depends on your provider and preferences. Instead I’ll walk through the key steps, so you understand what’s happening.

System: user and dependencies

The first rule — don’t run opencode as root. Create a separate user:

Terminal window
adduser --gecos "" --disabled-password developer
usermod -aG sudo developer

From the packages you’ll need: curl, ufw, fail2ban, unattended-upgrades, jq. All installed in one command:

Terminal window
apt update && apt install -y curl ufw fail2ban unattended-upgrades jq

Installing OpenCode and configuration

OpenCode is installed by the official installer, run as the created user:

Terminal window
su - developer -c 'curl -fsSL https://opencode.ai/install.sh | sh'

After installation the binary lives at ~/.local/bin/opencode.

The minimal configuration for the web server (~/.config/opencode/opencode.json):

{
"$schema": "https://opencode.ai/config.json",
"server": {
"port": 4096,
"hostname": "0.0.0.0"
}
}

Port 4096 is the standard one for OpenCode. hostname: 0.0.0.0 — listen on all interfaces, so Tailscale clients can connect. Security is provided by ufw, not by binding to localhost.

LLM providers (Anthropic, OpenAI, OpenRouter and others) are configured through the Web UI after the first login — keys are stored locally on the server.

A systemd service: to survive reboots

OpenCode Web should start automatically on boot and restart on crash. For that — a systemd unit in user mode (~/.config/systemd/user/opencode-web.service):

[Unit]
Description=OpenCode Web Server
After=network.target
[Service]
Type=simple
WorkingDirectory=%h
ExecStart=/home/developer/.local/bin/opencode web --port 4096 --hostname 0.0.0.0
Restart=on-failure
RestartSec=5
[Install]
WantedBy=default.target

An important nuance: user systemd requires lingering — otherwise the service dies when the user logs out:

Terminal window
loginctl enable-linger developer

After that we run:

Terminal window
su - developer -c "systemctl --user daemon-reload"
su - developer -c "systemctl --user enable --now opencode-web"

We check that the server responds:

Terminal window
curl -s http://localhost:4096/global/health

What came out of it: Web UI, phone, and API

After setup I have one server, accessible three ways.

Web GUI from the phone

I open the browser on my phone, type in the server’s Tailscale address (http://100.x.x.x:4096) — and land in the full OpenCode interface. I can start a new session or resume an existing one. It works exactly like the local TUI: the same agents, the same MCP servers, the same filesystem access rights.

The LLM provider and API key are configured once through the Web UI — and available from any device on the Tailscale network.

TUI from the laptop

From the laptop (also on the Tailscale network) I connect via opencode attach:

Terminal window
opencode attach http://100.x.x.x:4096

That gives a full TUI interface that talks to the same backend. You can resume a session started from the phone, or vice versa — start in the terminal and continue in the browser.

Why this matters: the TUI is more convenient for intensive code work (keyboard, fast navigation). The Web UI is for quick questions from the phone. Sessions are shared.

API out of the box

A non-obvious bonus: opencode web brings up the same REST API that opencode serve does. The clients (TUI, Web UI) work through it too. This means you can automate work with the agent:

Essentially, you get an AI worker that lives on your server and is reachable via API at any moment. No need to spin up a separate instance for each task — one server handles every scenario.

Verdict: is it worth it

I’ve been using this setup for a few weeks now. Here’s an honest breakdown.

Performance: on the 2 vCPU / 8 GB RAM configuration, opencode-web runs with a wide margin. Average CPU utilization is 20%, even accounting for active development and git operations. No crashes, no slowdowns.

Pros:

Cons and limitations:

When this makes sense:

When it’s simpler to take something ready-made:

For myself the answer is unambiguous: yes, it’s worth it. The ability to pull out my phone and ask the agent anything — with my keys, my MCP servers, my configuration — outweighs the server cost.