Self-Host a Browser-Based Dev Env (Coder/code-server)
Install code-server or Coder OSS on a Linux server, configure workspaces, harden authentication, and expose the browser IDE securely via nginx and Let's Encrypt.
Before you start
- ▸A Linux server with a public IP and a DNS A record pointing to it
- ▸sudo or root access on the server
- ▸Ports 80 and 443 open at the network/cloud firewall level
- ▸Docker installed if using Coder OSS with the Docker workspace template
Running VS Code in a browser means your dev environment lives on a server with real CPU, RAM, and persistent storage — not on whatever laptop you happen to have with you. code-server gives you a single-user VS Code instance; Coder OSS adds multi-workspace orchestration on top of that foundation. This guide covers both, then puts either behind an nginx reverse proxy with TLS.
Choosing: code-server vs Coder OSS
code-server is the right choice for a single developer who wants one persistent remote workspace. Coder OSS (the open-source platform, not the SaaS product) suits teams or anyone who wants templated, reproducible workspaces they can create and destroy on demand. Both are maintained by Coder Technologies under the MIT license.
- code-server — single binary, runs as a systemd service, exposes one VS Code session.
- Coder OSS — server + agent model; workspaces are Terraform-defined, can run locally or on remote VMs/containers.
Prerequisites and Server Sizing
A usable single-developer setup needs at minimum 2 vCPUs and 4 GB RAM. For Coder OSS with multiple workspaces, plan 2 GB RAM per concurrent workspace plus 2 GB for the control plane. Use a fresh LTS server — Ubuntu 22.04/24.04, Debian 12, or Rocky 9 all work well.
Option A: Install code-server
Install via the official script
The upstream install script detects your distro and installs the appropriate package, then enables the systemd unit. Review the script before piping to bash on production boxes.
curl -fsSL https://code-server.dev/install.sh | sh
On Debian/Ubuntu the script adds the Coder apt repo. On Fedora/RHEL it adds the dnf repo. On Arch it uses the AUR wrapper or falls back to the binary. You can also install manually:
# Debian/Ubuntu — manual apt
curl -fsSL https://packagecloud.io/coder/code-server/gpgkey \
| sudo gpg --dearmor -o /usr/share/keyrings/code-server-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/code-server-keyring.gpg] \
https://packagecloud.io/coder/code-server/ubuntu jammy main" \
| sudo tee /etc/apt/sources.list.d/code-server.list
sudo apt update && sudo apt install -y code-server
# Fedora/RHEL/Rocky
sudo dnf config-manager --add-repo \
https://packagecloud.io/coder/code-server/config/el/9/config.repo
sudo dnf install -y code-server
# Arch (AUR)
paru -S code-server
Configure code-server
The per-user config lives at ~/.config/code-server/config.yaml. Enable it for a specific user (here, dev) by editing that file before enabling the service.
sudo mkdir -p /home/dev/.config/code-server
sudo tee /home/dev/.config/code-server/config.yaml <<'EOF'
bind-addr: 127.0.0.1:8080
auth: password
password: "changeme-use-something-long"
cert: false
EOF
sudo chown -R dev:dev /home/dev/.config
Keep bind-addr on loopback — nginx will front it. Set cert: false because TLS terminates at the proxy.
Enable and start the systemd service
sudo systemctl enable --now code-server@dev
sudo systemctl status code-server@dev
Option B: Install Coder OSS
Install the Coder binary
curl -fsSL https://coder.com/install.sh | sh
This installs /usr/local/bin/coder and a systemd unit. On RHEL-family systems without curl: sudo dnf install -y curl first.
Configure Coder's environment
Coder reads configuration from environment variables. Create a drop-in for the systemd unit:
sudo mkdir -p /etc/coder.d
sudo tee /etc/coder.d/coder.env <<'EOF'
CODER_ACCESS_URL=https://coder.example.com
CODER_HTTP_ADDRESS=127.0.0.1:3000
CODER_POSTGRES_URL=
EOF
For a single-server setup, leave CODER_POSTGRES_URL empty — Coder will use its embedded PostgreSQL-compatible database. For production, point it at a real PostgreSQL 14+ instance.
Enable the Coder service
sudo systemctl enable --now coder
sudo systemctl status coder
On first run, Coder prints an admin setup URL to the journal:
sudo journalctl -u coder -n 50 --no-pager
Open that URL (via SSH port-forward if the server isn't yet behind a proxy), create the admin account, then return here to finish the proxy setup.
Reverse Proxy with nginx and TLS
Both code-server and Coder require WebSocket support. nginx handles this cleanly. Obtain a cert first — Let's Encrypt via certbot is the standard choice.
Install nginx and certbot
# Debian/Ubuntu
sudo apt install -y nginx certbot python3-certbot-nginx
# Fedora/RHEL/Rocky
sudo dnf install -y nginx certbot python3-certbot-nginx
# Arch
sudo pacman -S nginx certbot certbot-nginx
Write the nginx server block
Replace code.example.com with your domain and 127.0.0.1:8080 with the upstream port (3000 for Coder OSS).
sudo tee /etc/nginx/sites-available/codeserver <<'EOF'
server {
listen 80;
server_name code.example.com;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection upgrade;
proxy_set_header Accept-Encoding gzip;
proxy_read_timeout 86400;
}
}
EOF
sudo ln -s /etc/nginx/sites-available/codeserver \
/etc/nginx/sites-enabled/codeserver
sudo nginx -t && sudo systemctl reload nginx
On Fedora/RHEL/Rocky, drop the file in /etc/nginx/conf.d/codeserver.conf — there is no sites-enabled directory by default.
Issue a TLS certificate
sudo certbot --nginx -d code.example.com --agree-tos -m [email protected]
Certbot rewrites the nginx block to add the 443 listener and redirect HTTP. The certificate auto-renews via a systemd timer (certbot.timer). Verify: sudo systemctl status certbot.timer.
Firewall Rules
Block direct access to the upstream ports. Allow only 80, 443, and SSH.
# ufw (Debian/Ubuntu)
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable
# firewalld (Fedora/RHEL/Rocky)
sudo firewall-cmd --permanent --add-service=ssh
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload
Authentication Hardening
code-server supports password auth out of the box; the password is hashed before storage. For stronger auth, disable the built-in password and put HTTP Basic Auth or an OAuth2 proxy (such as oauth2-proxy) in front of nginx instead.
Coder OSS supports OIDC natively. In /etc/coder.d/coder.env:
CODER_OIDC_ISSUER_URL=https://accounts.google.com
CODER_OIDC_CLIENT_ID=your-client-id
CODER_OIDC_CLIENT_SECRET=your-client-secret
CODER_OIDC_EMAIL_DOMAIN=yourdomain.com
Restart Coder after editing: sudo systemctl restart coder.
Coder Workspaces and Templates
Workspaces in Coder OSS are defined by Terraform templates. The quickstart template runs a workspace as a local Docker container. Install Docker first, then from a machine with the coder CLI logged in:
coder login https://coder.example.com
coder template init # choose "Docker" from the menu
cd <template-dir>
coder template push my-docker-template
coder create my-workspace --template my-docker-template
Each workspace runs a Coder agent inside the container that handles SSH, VS Code, and terminal forwarding back to the control plane. Users open their workspace at https://coder.example.com and get a full VS Code interface in the browser.
Verification
# code-server
curl -I https://code.example.com
# expect: HTTP/2 200 and a Set-Cookie header from code-server
# Coder OSS
coder users list
coder workspaces list
Open the URL in a browser without any SSH tunnel. You should see the login screen over HTTPS. Load a terminal inside the browser editor and run hostname — it should return the server name (or container ID for Coder workspaces).
Troubleshooting
- 502 Bad Gateway: The upstream service isn't running or is bound to the wrong address. Check
sudo systemctl status code-server@devorsudo systemctl status coderand confirm the port withss -tlnp | grep 8080. - WebSocket disconnect / terminal drops: Missing
proxy_set_header UpgradeorConnection upgradein the nginx block, or a load balancer timeout. Increaseproxy_read_timeout. - Certificate not trusted: Certbot may have failed DNS propagation. Run
sudo certbot renew --dry-runand check for errors. - Coder agent offline: The workspace container can't reach the control plane URL. Confirm
CODER_ACCESS_URLis publicly reachable and that the firewall allows egress from the container to port 443. - SELinux blocking nginx proxy (RHEL/Rocky): Run
sudo setsebool -P httpd_can_network_connect 1.
Frequently asked questions
- Can I run code-server and Coder OSS on the same machine?
- Yes, but there is little reason to. Coder OSS already embeds a VS Code server in each workspace via its agent. Run one or the other to avoid port conflicts and maintenance overhead.
- Is the browser VS Code experience identical to the desktop app?
- Nearly identical. Most extensions work, but a small number that call native OS APIs (certain debugger adapters, GPU-accelerated rendering) will not function or will behave differently in a browser context.
- How do I persist VS Code extensions and settings across code-server restarts?
- Extensions and user settings are stored in ~/.local/share/code-server and ~/.config/code-server respectively in the running user's home directory. As long as that home directory persists — on the host or a mounted volume — settings survive restarts and upgrades.
- Can multiple users share one code-server instance?
- No. code-server is single-user by design. For multi-user access, deploy Coder OSS, which gives each user their own isolated workspace, or run separate code-server instances on different ports under different system accounts.
- What happens to a Coder workspace when the server reboots?
- The Coder control plane restarts via systemd. Workspaces that were running are marked stopped; users must manually start them again from the dashboard or CLI unless you configure an auto-start policy in the workspace template.
Related guides
Configure Prometheus Alertmanager
Configure Prometheus Alertmanager with routing trees, receivers, inhibition rules, grouping, Go templates, and PagerDuty/Slack on-call integrations.
Build an Intranet Server on Linux
Set up a complete small-office intranet on one Linux box: Nginx web server, dnsmasq local DNS, Samba file sharing, and a Wiki.js team wiki.
Build an nftables Firewall Script
Build a complete nftables firewall from scratch: tables, chains, sets, default-deny input policy, service allowlisting, and persistent systemd configuration.
Caddy as a Reverse Proxy
Set up Caddy as a reverse proxy with automatic HTTPS, load balancing, WebSocket passthrough, reusable snippets, and header control — no certbot required.