$linuxjunkies
>

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.

AdvancedUbuntuDebianFedoraArch12 min readUpdated June 7, 2026

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@dev or sudo systemctl status coder and confirm the port with ss -tlnp | grep 8080.
  • WebSocket disconnect / terminal drops: Missing proxy_set_header Upgrade or Connection upgrade in the nginx block, or a load balancer timeout. Increase proxy_read_timeout.
  • Certificate not trusted: Certbot may have failed DNS propagation. Run sudo certbot renew --dry-run and check for errors.
  • Coder agent offline: The workspace container can't reach the control plane URL. Confirm CODER_ACCESS_URL is 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.
tested on:Ubuntu 24.04Debian 12Rocky 9Arch rolling

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