Skip to content

Kasm Windows Agent Will Not Connect

When to use this

Use this when the Windows server workspace (K8) is unreachable from Kasm. Typical symptoms:

  • No resources are available to create the requested Kasm when launching a session
  • Server shows missing under Admin > Infrastructure > Servers
  • Last Reported is hours or days old
  • logs.txt on the Windows host is growing rapidly

Check the easy thing first: No resources are available is most often a stale session, not an agent problem. The server allows one concurrent user, so an old session (including a forgotten browser tab on another machine) blocks all new ones. Go to Admin > Sessions, clear filters, and delete any session against K8. If the server shows healthy with a current Last Reported, this is your problem, not the agent.

Environment reference

Item Value
Kasm deployment 192.168.2.27 (LXC on Proxmox), port 443
Windows server K8 / k8.home / 192.168.2.28
Server ID 5d1dc5da-da73-4123-ae19-02559b37d5bd
Agent install path C:\Program Files\Kasm
Agent config C:\Program Files\Kasm\config.yaml
Agent log C:\Program Files\Kasm\logs.txt
Agent listen port 4902 (inbound, from Kasm)
Installer kasm_windows_service_installer_x86_64_1.8_8e3b288d3.exe (pairs with Workspaces 1.19.0)

Background: why this happens

The agent holds a JWT in config.yaml (agent_jwt_token) with a 3-day lifetime. It refreshes on a rolling schedule. If K8 is powered off or asleep past that window, the token expires and the agent cannot recover on its own - it 403s on every refresh forever.

The per-server registration token from the admin UI is long-lived (10 years) and is only used for enrollment. It does not rescue an expired agent token.

Step 1: Diagnose

On K8 (elevated PowerShell)

Get-Service -Name "Kasm" | Format-List Name,Status,StartType
Get-NetTCPConnection -LocalPort 4902 -State Listen -ErrorAction SilentlyContinue
Get-NetRoute -DestinationPrefix "192.168.2.0/24" | Select-Object InterfaceAlias,NextHop,RouteMetric,InterfaceMetric
Select-String -Path "C:\Program Files\Kasm\config.yaml" -Pattern "agent_jwt_token_exp|api_host|api_port|registered"
Get-Content "C:\Program Files\Kasm\logs.txt" -Tail 30
Get-Item "C:\Program Files\Kasm\logs.txt" | Select-Object Length,LastWriteTime

Convert the token expiry to a readable date:

$exp = (Select-String -Path "C:\Program Files\Kasm\config.yaml" -Pattern "agent_jwt_token_exp:\s*(\d+)").Matches.Groups[1].Value
[DateTimeOffset]::FromUnixTimeSeconds([int64]$exp).ToLocalTime()

Test the path to Kasm:

Test-NetConnection -ComputerName 192.168.2.27 -Port 443 -InformationLevel Detailed

On the Kasm LXC (SSH as root)

docker ps --format '{{.Names}}\t{{.Status}}'
docker logs --since 15m kasm_api 2>&1 | grep -iE "register_component|jwt|403|5d1dc5" | tail -40

Confirm the LAN path to the agent:

timeout 5 bash -c 'cat < /dev/null > /dev/tcp/k8.home/4902' && echo "4902 OPEN" || echo "4902 CLOSED/FILTERED"
timeout 5 bash -c 'cat < /dev/null > /dev/tcp/k8.home/3389' && echo "3389 OPEN" || echo "3389 CLOSED/FILTERED"

Reading the results

Log signature Cause Go to
The JWT token has expired + 403 on refresh Expired agent token Step 3 (reinstall)
ConnectTimeout to an IP Agent pointed at a stale address Step 3 (reinstall with correct host)
Test-NetConnection shows InterfaceAlias: Tailscale Tailscale hijacking the LAN route Step 2 first
Service Paused nssm gave up after repeated start failures Step 3 (reinstall)
Clean log, service running, healthcheck 200 Agent is fine Check for a stale session instead

Step 2: Fix routing if Tailscale is in the path

K8 and Kasm are both on 192.168.2.0/24. If pfSense advertises that subnet over Tailscale and K8 accepts it, Tailscale wins the route (metric 0/5 vs 256/20) even though the machine sits on that segment physically. Registering through that path is fragile - it breaks whenever Tailscale reshuffles.

On K8 (elevated PowerShell)

tailscale set --accept-routes=false --accept-dns=false
Test-NetConnection -ComputerName 192.168.2.27 -Port 443 -InformationLevel Detailed

You want InterfaceAlias to be the physical NIC (Ethernet 3) and SourceAddress to be 192.168.2.28. These settings persist across reboots, but Tailscale auto-updates, so re-check after a client update.

Step 3: Uninstall and reinstall the agent

This is the fix. Editing config.yaml by hand, dropping in registration_token.txt, or re-running register.exe do not work on an expired agent (see Known Gotchas).

On K8 (elevated PowerShell)

sc.exe stop Kasm
& "C:\Program Files\Kasm\uninstall.exe"

Verify it is gone:

Get-Service -Name "*kasm*" -ErrorAction SilentlyContinue
Test-Path "C:\Program Files\Kasm"

In the Kasm admin UI

  1. Go to Admin > Infrastructure > Servers, find K8, click Edit.
  2. Confirm IP/Hostname is 192.168.2.28.
  3. In the Kasm Agent section, copy the Registration Token.

Do not use Server Enrollment Tokens - those create new server records and will orphan the existing one along with its workspace bindings.

On K8 (elevated PowerShell)

Run the installer elevated. Use the GUI so it reports which validation step fails:

  • Hostname/IP: 192.168.2.27
  • Port: 443
  • Registration Token: paste from the admin UI
  • Proxy Path: leave blank

Registration validates three things:

  1. The agent can reach Kasm on the given host and port
  2. Kasm can reach the agent back on port 4902 at the address in the Server record
  3. The token is properly signed, unexpired, and issued for this server

Verify

sc.exe query Kasm
Select-String -Path "C:\Program Files\Kasm\config.yaml" -Pattern "agent_jwt_token_exp|registered"
Get-Content "C:\Program Files\Kasm\logs.txt" -Tail 15

Success looks like:

  • Service RUNNING
  • JWT has been refreshed. New token expires <date ~3 days out>
  • Heartbeat result: Service registered as running
  • Starting Kasm Upload Server ... On Port 4902
  • GET https /__healthcheck? 200 OK
  • No 403 errors

Then confirm the Servers page shows K8 healthy with a current Last Reported and the Server ID unchanged, and launch a session.

Step 4: Prevention

The root cause is K8 sleeping past its 3-day token window.

On K8 (elevated PowerShell)

powercfg /change standby-timeout-ac 0
powercfg /hibernate off

Also worth checking periodically:

Get-Item "C:\Program Files\Kasm\logs.txt" | Select-Object Length,LastWriteTime

There is no log rotation. A failing agent writes a line roughly every 1.7 seconds - the last incident produced a 93 MB file in six weeks.

Different component, different fix. Symptoms are Error, expired JWT flooding kasm_api logs, kasm_guac unhealthy, and browser RDP sessions failing while container workspaces (Chromium, Ubuntu) still work fine.

Container workspaces are KasmVNC and do not touch Guac. RDP to a server workspace does. If only the Windows box is broken, check Guac.

Unlike the Windows agent, Guac can be fixed in place.

In the Kasm admin UI

Copy the Component Registration Token (Global Settings - search the sidebar for "registration"). This is different from the per-server registration token.

On the Kasm LXC (SSH as root)

printf '%s' 'PASTE_TOKEN_HERE' > /opt/kasm/current/conf/app/guac/registration_token.txt
chown kasm:kasm /opt/kasm/current/conf/app/guac/registration_token.txt
chmod 600 /opt/kasm/current/conf/app/guac/registration_token.txt
docker restart kasm_guac

Verify - the token file is consumed and deleted on success:

ls -la /opt/kasm/current/conf/app/guac/registration_token.txt
docker inspect --format '{{.State.Health.Status}} {{.State.Health.FailingStreak}}' kasm_guac
docker logs --since 2m kasm_guac 2>&1 | grep -c 401

Want healthy, FailingStreak 0, and 0 matches. Note that docker logs retains output from before a restart, so use a --since window shorter than the container uptime or you will see stale 401s.

The same registration_token.txt mechanism applies to other components, placed alongside each one's config file:

  • Agent: /opt/kasm/current/conf/app/agent/agent.app.config.yaml
  • Guac: /opt/kasm/current/conf/app/guac/kasmguac.app.config.yaml
  • RDP Gateway: /opt/kasm/current/conf/app/rdp_gateway/

Known gotchas

These do not work on an expired Windows agent. Confirmed by testing:

  • Dropping registration_token.txt into C:\Program Files\Kasm. The agent logs Loaded registration token from ... and then submits the expired token from config.yaml anyway.
  • Running register.exe (the GUI registration dialog). Registration succeeds server-side and heartbeat starts, but the agent never writes the new token to config.yaml, so it keeps 403ing on refresh.
  • Setting registered: false in config.yaml. The agent flips it back to true on startup and changes nothing else.

The Guac registration_token.txt mechanism works. The Windows agent equivalent does not. Do not assume they behave the same way.

Other notes:

  • After repeated start failures, nssm leaves the service in SERVICE_PAUSED. Windows rejects a START control on a paused service. Use sc.exe stop Kasm (or sc.exe continue Kasm), not nssm start. If it will not stop: Stop-Process -Name "agent" -Force.
  • To park a flapping service while working: & "C:\Program Files\Kasm\nssm.exe" set Kasm Start SERVICE_DEMAND_START. Remember to set it back to SERVICE_AUTO_START.
  • The installer version line ("1.8") is not the Workspaces version. Check the compatibility column in the Kasm docs installer table.
  • The agent bakes api_host into config.yaml at install time. It does not follow the Kasm deployment if it moves. Reinstall after any address change.
  • Session teardown on server workspaces: Delete Session when done, Leave this session to come back to it. Closing the tab leaves the session running and blocks the single concurrency slot.

Appendix: decoding a JWT in PowerShell

Useful for confirming a token's server_id and expiry.

$t = (Get-Content "C:\Program Files\Kasm\registration_token.txt" -Raw).Trim()
$p = $t.Split('.')[1]
switch ($p.Length % 4) { 2 { $p += '==' } 3 { $p += '=' } }
$json = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($p.Replace('-','+').Replace('_','/')))
$json
[DateTimeOffset]::FromUnixTimeSeconds(($json | ConvertFrom-Json).exp).ToLocalTime()

Incident history

Date Component Cause Fix
2026-05-22 Windows agent Installed pointing at 192.168.2.144; Kasm later migrated to 192.168.2.27 LXC. Silent connect timeouts for months. Reinstall against .27
2026-06-28 Guac Token marked consumed during 1.18.1 to 1.19.0 upgrade when refresh errored (known bug, fixed in 1.19.0 but does not revive an already-dead token) registration_token.txt
2026-09-05 Windows agent K8 asleep past the 3-day token window Uninstall and reinstall