Skip to main content
Open Source Solutions

Kasm Workspaces: Self-Hosted Browser Isolation Done Right

How I deploy Kasm Workspaces for browser isolation on a single VPS, the Caddy proxy in front, and where remote browsers actually beat RDP and VDI.

Published Updated 10 min read

I’ve been running Kasm Workspaces as the browser isolation layer for a handful of security-sensitive engagements over the past two years, and it’s the self-hosted remote-browser platform I reach for when “open this link in a sandbox” needs to actually mean something. It runs on a single VPS, streams containerised browsers and desktops to anyone with a web browser, and the Community Edition is free for up to five concurrent sessions.

This post is the install I actually ship: the single-server script, a custom port to keep the admin UI off scanner radar, Caddy in front for clean HTTPS, and the threat-model thinking that decides when remote browser isolation is overkill versus when it’s the only sane option.

If you’ve never deployed Kasm, read the prerequisites and the threat-model section before you touch the installer. The install itself is the easy part. Knowing whether you should be running this is the harder question.

Why a self-hosted browser isolation platform is worth running

Most browser-isolation conversations start with “open the link in a VM”. That mental model is right but the operational reality is wrong. Spinning up a fresh VM per click doesn’t scale to humans, and “just use the throwaway laptop” lasts about four hours before colleagues are back on their main machine because the workflow is too slow.

Kasm solves the operational problem. The user clicks a link, a browser opens in a new tab, and the page renders inside a container on the server. Nothing executes on the user’s laptop except the streaming client. When they close the tab, the container is destroyed. There’s no state for a malicious page to infect because the laptop never ran the page.

A reply-all phishing chain landed in a finance team’s inbox last spring with a “shared invoice” link to a domain forty minutes old. Inside Kasm, opening the link was a non-event. On a user’s laptop, it would have run a credential-stealer. That single avoided incident pays for the year of running the box.

What remote browser isolation doesn’t fix is the human in the loop. If the person inside the Kasm session pastes their real password into the phishing form, the container dutifully hands it over. Browser isolation contains the technical blast radius. The human element in cybersecurity defense is the layer that contains the rest, and you need both.

The Kasm Workspaces deployment stack

One installer script for Kasm, Caddy in front for TLS, and that’s the whole stack. No Compose file because Kasm’s install handles its own container orchestration.

Prerequisites

A VPS with at least 2 vCPU, 4 GB of RAM, and 50 GB of disk. That’s the bare minimum. For real use with 5–10 concurrent sessions I run a 4 vCPU / 8 GB instance with 80 GB of disk; the image cache fills quickly.

You also need a hardened server before anything else lands on it. SSH keys, no root login, UFW with deny-by-default, fail2ban or CrowdSec. My Linux server security fundamentals post is the baseline I run on every fresh box. A browser isolation server on an unhardened host is the worst kind of false confidence.

You’ll also need DNS for the domain, ideally a provider with API-driven Let’s Encrypt challenges. Cloudflare is the pragmatic default.

Before bringing it up, decide whether the admin URL needs to be public at all. For an internal team I keep Kasm reachable only over a Wireguard tunnel, with the patterns from the Wireguard Easy and Mistborn posts. For an external-auditor or client-facing use case it has to be public, and that’s where the reverse proxy hardening matters more.

Docker Engine

Install Docker from the official Docker repository. Follow the official installation guide for Ubuntu. Don’t install from the distro’s default apt repo, and don’t use a one-line curl | sh script you found in someone’s blog post.

docker --version
sudo systemctl enable --now docker

Kasm Workspaces installer

Kasm Technologies publishes the installer as a tarball on S3. For everything in this post you want the single-server online deployment, the path the official docs flag as standard for sub-50-user environments.

Pull and extract the latest release. Always check the Kasm install docs for the current version number rather than copying it from this post.

cd /tmp
curl -O https://kasm-static-content.s3.amazonaws.com/kasm_release_1.14.0.3a7abb.tar.gz
tar -xf kasm_release_1.14.0.3a7abb.tar.gz

Run the installer with a custom HTTPS port. The default is 8443 and I’d skip it. Custom ports don’t add much real security, but they cut the noise from internet-wide scanners by at least an order of magnitude, which makes the actual signals in the access logs readable.

sudo bash kasm_release/install.sh -L 9443

The installer will prompt for swap configuration, accept the licence terms, and then download the workspace images. On a 4 vCPU box this takes 15–25 minutes depending on network. When it finishes, it prints the admin and user passwords. Save these immediately to your password manager. The installer does not show them a second time.

Caddy as the reverse proxy

Caddy is the simplest reverse proxy I know of for a one-host deployment. It handles TLS termination and Let’s Encrypt renewal with no config beyond the domain and upstream. For Kasm specifically, the upstream is HTTPS with a self-signed certificate, so the proxy needs to skip TLS verification on the internal hop.

Install Caddy from the official Cloudsmith-hosted Debian repository.

sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' \
  | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' \
  | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update && sudo apt install -y caddy

Edit /etc/caddy/Caddyfile and replace whatever default content is there with the block below. Change kasm.example.com to your real domain and 9443 to whatever port you passed to -L during the Kasm install.

kasm.example.com {
    reverse_proxy https://127.0.0.1:9443 {
        transport http {
            tls_insecure_skip_verify
        }
    }
}

Reload Caddy.

sudo systemctl reload caddy

Caddy will request a certificate from Let’s Encrypt the first time the domain is hit, set up the redirect from HTTP to HTTPS, and start proxying. If the certificate request fails, the most common cause is the DNS A record not yet propagating; give it five minutes and try again.

First-login setup

Browse to https://kasm.example.com and log in with the admin password from the installer output. Immediately:

  1. Change the admin password to something long and unique.
  2. Create a separate non-admin user for daily use; the admin account is for configuration, not opening sessions.
  3. Review the pre-loaded Workspaces images. Chrome and Firefox are what most teams want first; Edge, Brave, and a handful of containerised apps are also there.
  4. Configure SSO under Settings → Authentication if you have an identity provider. Plugging Kasm into Authentik over OIDC takes about ten minutes.

Optional day-one work: configure the inactivity timeout to fifteen minutes, and review network policy defaults if you intend to restrict outbound traffic from sessions.

Sizing, scale, and where the limits actually bite

The published minimums are honest but understate the experience. A 2 vCPU / 4 GB box runs a single session, but you’ll feel the lag the moment a page loads anything heavy. Real numbers from boxes I’ve actually run:

  • 4 vCPU / 8 GB / 80 GB SSD: comfortable for 5 concurrent users on browser-only workflows. CPU at 40–60% at peak. My default.
  • 8 vCPU / 16 GB / 160 GB SSD: comfortable for 10 concurrent users with a mix of browsers and lightweight desktop sessions.
  • Anything below 2 vCPU: don’t. The streaming codec is real work and a single small core will saturate during normal browsing.

The Community Edition’s hard cap is five concurrent sessions. If you need more, you’re either on a paid Kasm tier or running multiple Community installs split by team.

The other limit that surprises people is outbound bandwidth. Each active session streams pixels at roughly 0.5–2 Mbps. Five concurrent users on a 100 Mbps link is fine. A hundred concurrent users on a 1 Gbps link will hit the network before the CPU.

When browser isolation is the right call, and when it isn’t

This is the section the install guides skip. Kasm is not free in the operational sense even when the licence is. Decide before you roll it out.

I’d reach for Kasm first when opening untrusted inbound attachments, accessing high-blast-radius admin panels (cloud consoles, payment processors, identity providers) from devices whose endpoint posture I can’t fully verify, demoing internal tooling to external auditors without provisioning them VPN access, letting short-term contractors interact with internal apps, or running actual malware samples for research.

Kasm is overkill for general office browsing, where latency and clipboard quirks erode adoption inside a week. It’s also overkill for day-job admin work on systems your own laptop is already properly hardened for. Browser isolation is a layer for untrusted targets, not a replacement for endpoint security. For the general “make our browsing safer” goal, a hardened browser on a hardened laptop with sensible DNS filtering does most of the job.

What I don’t bother with on a Community deployment

A few things I’ve consciously left out of this stack for small-team use:

  • Multi-server agent pools. The Community Edition technically supports them, but the reason to scale horizontally is concurrent-session count, and you can’t grow past five anyway.
  • Custom workspace images. The bundled Chrome, Firefox, and Edge images cover the actual security-sensitive workflows. Custom images are worth the effort when you have a specific app to harden, not as day-one work.
  • Network ACLs per workspace. Powerful for compliance scenarios. Overkill for an internal team where the threat model is “untrusted external content, fully trusted internal user”.
  • Session recording. Useful for audit-heavy environments, a privacy minefield everywhere else. Don’t enable it without a written policy.

If you’re running this for a regulated industry where evidence-of-controls matters, the picture changes and recording, ACLs, and SSO all become non-negotiable.

Verifying the deployment before you trust it

Run this checklist before you point any team at the URL:

  1. Browse to the public URL on a device that has never connected. Verify the certificate is valid Let’s Encrypt, not Caddy’s interim self-signed.
  2. Open a Chrome workspace and load three test pages: benign, autoplay video, heavy JavaScript app. If video stutters, scale CPU first.
  3. Open the same workspace from a second device simultaneously. The two sessions should have no shared state. No cookies, no history, no logged-in accounts.
  4. End one session and start a new one. Confirm the new container has no leftover state from the previous one. This is the entire point of the platform.
  5. Stop and restart the Kasm services (sudo systemctl stop kasm && sudo systemctl start kasm). Verify users can log back in cleanly.
  6. From a different device, attempt to hit https://your-domain:9443 directly. Caddy in front means this should be blocked at the firewall. If it’s reachable, your firewall rules are wrong.

Step 6 is the one people skip. The point of Caddy in front is to make the admin port a single externally-visible HTTPS endpoint on 443. If 9443 is also open, your reverse proxy is decorative.

Closing the loop

This Kasm Workspaces stack has been my go-to remote-browser layer for sensitive client engagements since late 2023. Setup is under an hour including the security baseline, the recurring cost is whatever a 4 vCPU VPS runs in your region, and the operational overhead is negligible because Kasm handles its own container lifecycle.

What self-hosting Kasm has actually given me, beyond the obvious technical containment, is a clear answer to the “how do you handle untrusted content” question that comes up in every security-conscious client conversation. Pointing at a working remote browser and saying “we open it in here” is more reassuring than any policy document. The companion read for the rest of that conversation is the generic security guides section. Kasm is one layer in the stack, and it works best when the other layers are pulling their weight too.

Watch on YouTube

Video walkthrough

Prefer the screen-recording version of this guide? Watch it on YouTube — opens in a new tab so the player only loads when you ask for it.

Frequently Asked Questions

Want this handled, not just understood?

Reading the playbook is one thing. Running it on production at 2am is another. If you'd rather have me run it for you, the door is open.

Apply for Access