opencode-web on a VPS: an AI assistant in your pocket
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.
| Agent | Web access | How it works | What’s under the hood |
|---|---|---|---|
| Claude Code | ✅ Remote Control | claude remote-control → QR code → claude.ai/code or the Claude mobile app | A 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 UIs | The ChatGPT mobile app connects to the Codex App on macOS/Windows. Or npx rmtcdx / remcodex — third-party web interfaces | Officially: remote control through an OpenAI account. Unofficially: rmtcdx with Tailscale support, codex-remote-hub on ttyd+tmux |
| OpenCode | ✅ Native opencode web | One 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. |
| Aider | ❌ | None | Terminal, local only |
| Cline | ❌ | None | VS Code extension, local only |
| Cursor | ❌ | None | A 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:
- Claude Code Remote Control — official documentation
- Claude Code on the web — comparison of modes (web, Remote Control, terminal)
- Codex Remote Connections — official OpenAI documentation
- rmtcdx — Codex Remote Web Client — a third-party web UI for Codex with Tailscale support
- claude-code-web — self-hosted web UI for Claude Code over an SSH tunnel
- OpenCode CLI: attach — docs for
opencode attachto connect to a remote server
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:
| Provider | Configuration | Price | Notes |
|---|---|---|---|
| Oracle Cloud Free | 4 ARM / 24 GB | €0 | Registration is painful, may kill idle machines |
| Hetzner CX22 | 2 x86 / 4 GB | €3.79/mo | Best price/performance in Europe, x86 |
| Netcup VPS 1000 G11 | 2 x86 / 4 GB | €3.99/mo | 128 GB SSD — 3× more disk |
| RackNerd (sale) | 1 vCPU / 1 GB | ~$1/mo | Bare minimum, US only |
| Yandex Cloud | 2 vCPU / 2 GB | ~€17/mo | 3–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:
- Steal LLM API keys — and that’s real money. An attacker runs tokens on their own account while you sleep.
- Read and modify code in repository working copies — plant a backdoor, steal intellectual property.
- Execute arbitrary commands on the server — the server becomes an entry point into the rest of your infrastructure.
- Reach MCP servers — and those may have connections to databases, external APIs, internal services.
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
| Layer | What it protects | Tool |
|---|---|---|
| Network | Access to port 4096 from the internet | Tailscale + ufw |
| Application | Access to the Web UI | OAuth via Yandex ID |
| OS | SSH access | Keys 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 + fail2banTailscale: 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:
ufw allow in on tailscale0ufw --force enableNow 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:
curl -fsSL https://tailscale.com/install.sh | shtailscale upAfter 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 ↓ TailscaleCaddy :443 (reverse proxy, HTTPS) ↓auth-proxy (JS, 165 lines) ↓ token check ├─ Yandex ID OAuth └─ opencode-web :4096Caddy 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:
- Register an OAuth application in Yandex ID — you get a
client_idandclient_secret - The callback URL points at your server — after login, Yandex redirects back with the token
- The auth proxy verifies the token via Yandex’s API and either lets you through to opencode-web or returns 401
Why Yandex ID:
- No need to create separate passwords — you log in with your Yandex account
- Two-factor authentication out of the box
- OAuth tokens are short-lived, refresh tokens can be revoked
- Supports PKCE — a safe flow for browser clients
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:
# Keys only, no passwords, no root loginsed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_configsed -i 's/^#*PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_configsed -i 's/^#*PubkeyAuthentication.*/PubkeyAuthentication yes/' /etc/ssh/sshd_configsystemctl restart sshdPlus fail2ban to protect against SSH brute force:
apt install -y fail2bansystemctl enable --now fail2banAnd automatic security updates, so you don’t miss critical patches:
apt install -y unattended-upgradesCore 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:
adduser --gecos "" --disabled-password developerusermod -aG sudo developerFrom the packages you’ll need: curl, ufw, fail2ban, unattended-upgrades, jq. All installed in one command:
apt update && apt install -y curl ufw fail2ban unattended-upgrades jqInstalling OpenCode and configuration
OpenCode is installed by the official installer, run as the created user:
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 ServerAfter=network.target
[Service]Type=simpleWorkingDirectory=%hExecStart=/home/developer/.local/bin/opencode web --port 4096 --hostname 0.0.0.0Restart=on-failureRestartSec=5
[Install]WantedBy=default.targetAn important nuance: user systemd requires lingering — otherwise the service dies when the user logs out:
loginctl enable-linger developerAfter that we run:
su - developer -c "systemctl --user daemon-reload"su - developer -c "systemctl --user enable --now opencode-web"We check that the server responds:
curl -s http://localhost:4096/global/healthWhat 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:
opencode attach http://100.x.x.x:4096That 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:
- Run an agent on a schedule:
opencode run --attach http://100.x.x.x:4096 "check the PR and leave a review" - Write a script for nightly tasks
- Integrate with CI/CD: the agent checks the diff before deploy
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:
- AI in your pocket. Asking the agent a question from your phone in the subway or a cafe is genuinely convenient. Especially when you need to quickly check an idea or recall a syntax.
- Your key, your server. You pay the LLM provider directly — no SaaS markup for a “convenient interface.”
- Three in one. Web UI, TUI, and API all work with the same backend. Started in the terminal — continued on the phone.
- API for automation. You can wire the agent into your own workflows.
Cons and limitations:
- 4,800 ₽/mo for the server (Yandex Cloud, with headroom) or from €3.79/mo (Hetzner, the minimum). That’s an extra cost on top of paying for LLM tokens.
- Setup takes time. A couple of hours to install and debug, if you’ve never worked with a VPS and Tailscale before.
- It’s not an IDE. OpenCode doesn’t replace Cursor or Copilot for inline autocomplete in the editor. That’s a different scenario — agentic tasks, not autocomplete.
When this makes sense:
- You actively use AI agents and want access from different devices
- You already have your own API key for an LLM provider and don’t want to pay a SaaS surcharge
- You need an AI worker for automation (PR checks, nightly tasks)
When it’s simpler to take something ready-made:
- You only need inline autocomplete in your IDE — take Cursor or Copilot
- You’re willing to pay for convenience and don’t want to fiddle with a server — Claude Code on the web
- You need AI once a week — keeping a server running just for that isn’t justified
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.