Skip to content

Instantly share code, notes, and snippets.

@startergo
Last active March 1, 2026 06:55
Show Gist options
  • Select an option

  • Save startergo/8b2f264e1b68d4f354cb92bb00bb723e to your computer and use it in GitHub Desktop.

Select an option

Save startergo/8b2f264e1b68d4f354cb92bb00bb723e to your computer and use it in GitHub Desktop.
USB/IP Windows — Share a Physical USB Device Over the Network

USB/IP Windows — Share a Physical USB Device Over the Network

Share a locally attached USB device (e.g. a license dongle or USB mass storage key) from one Windows machine to another so it appears as a native physical USB device on the remote end.

Stack used:

  • Host side: usbipd-win — shares the USB device over the network
  • Client side: usbip-win2 — attaches the remote USB device as if it were locally plugged in
  • Optional networking: Tailscale — simplifies connectivity across networks and eliminates firewall configuration

Table of Contents

  1. Prerequisites
  2. Architecture Overview
  3. Host Setup (Machine with the USB device)
  4. Client Setup (Remote machine)
  5. Using Tailscale (Recommended for cross-network or domain scenarios)
  6. Domain-Joined Host: GPO Firewall Considerations
  7. Detaching and Cleanup
  8. Reconnecting After Reboot
  9. Troubleshooting
  10. Security Considerations
  11. Quick Reference Cheatsheet

Prerequisites

Requirement Host Client
Windows 10 (1903+) or Windows 11
Administrator / elevated PowerShell
Network connectivity between machines
USB device physically plugged in
usbipd-win installed
usbip-win2 installed

Note: Both machines must be able to reach each other over TCP port 3240 (standard USB/IP protocol port), unless using Tailscale or the SSH tunnel workaround described below.


Architecture Overview

Standard (direct connection, same network)

┌─────────────────────────────────┐         ┌─────────────────────────────────┐
│           HOST MACHINE          │         │          CLIENT MACHINE         │
│                                 │         │                                 │
│  ┌─────────────┐                │  TCP    │                ┌─────────────┐  │
│  │ USB Dongle  │──► usbipd-win ─┼───3240──┼──► usbip-win2  │ Virtual USB │  │
│  │  (physical) │                │         │                │   Device    │  │
│  └─────────────┘                │         │                └─────────────┘  │
└─────────────────────────────────┘         └─────────────────────────────────┘

Tailscale (recommended — works across any network, bypasses firewall complexity)

┌─────────────────────────────────┐         ┌─────────────────────────────────┐
│           HOST MACHINE          │         │          CLIENT MACHINE         │
│                                 │         │                                 │
│  ┌─────────────┐                │Tailscale│                ┌─────────────┐  │
│  │ USB Dongle  │──► usbipd-win ─┼─ VPN ───┼──► usbip-win2  │ Virtual USB │  │
│  │  (physical) │                │  :3240  │                │   Device    │  │
│  └─────────────┘                │         │                └─────────────┘  │
└─────────────────────────────────┘         └─────────────────────────────────┘

SSH tunnel (when port 3240 is blocked by GPO/firewall, no Tailscale)

┌─────────────────────────────────┐         ┌─────────────────────────────────┐
│           HOST MACHINE          │         │          CLIENT MACHINE         │
│                                 │         │                                 │
│  ┌─────────────┐                │  SSH    │  SSH tunnel  ┌─────────────┐    │
│  │ USB Dongle  │──► usbipd-win ─┼───22────┼──► localhost ─► usbip-win2 │    │
│  │  (physical) │                │         │     :3240    │ Virtual USB │    │
│  └─────────────┘                │         │              └─────────────┘    │
└─────────────────────────────────┘         └─────────────────────────────────┘

Host Setup (Machine with the USB device)

All commands below must be run in an elevated (Administrator) PowerShell.

1. Install usbipd-win

Option A — via winget (recommended):

winget install usbipd

Option B — manual installer: Download the latest .msi from the usbipd-win releases page and run it.

After installation, close and reopen PowerShell so the new PATH entry takes effect.

Verify the install:

usbipd --version

usbipd-win installs a Windows Service that starts automatically. You do not need to run usbipd server manually.


2. Find and bind the device

List all connected USB devices:

usbipd list

Example output:

BUSID  VID:PID    DEVICE                          STATE
2-9    0781:5571  USB Mass Storage Device         Not shared
3-1    046d:c52b  USB Input Device                Not shared

Note the BUSID of your device (e.g. 2-9). Bind it — this marks it as available for sharing and persists across reboots:

usbipd bind --busid 2-9

Verify the state changed:

usbipd list
# STATE column should now show: Shared

bind only needs to be run once. The binding and the usbipd service both persist across reboots automatically.


3. Open the firewall

Add a local inbound rule for port 3240:

New-NetFirewallRule `
  -DisplayName "usbipd USB/IP" `
  -Direction Inbound `
  -Protocol TCP `
  -LocalPort 3240 `
  -Action Allow `
  -Profile Domain,Private,Public

Verify the rule was created and is active:

Get-NetFirewallRule -DisplayName "usbipd USB/IP" | `
  Select-Object DisplayName, Enabled, Action, PolicyStoreSourceType

Expected output:

DisplayName    Enabled  Action  PolicyStoreSourceType
-----------    -------  ------  ---------------------
usbipd USB/IP    True   Allow   Local

If the host is domain-joined, check whether GPO is overriding local firewall rules:

netsh advfirewall show allprofiles

If you see LocalFirewallRules: N/A (GPO-store only) under any profile, local rules are completely ignored by GPO and the rule above will have no effect. See Domain-Joined Host: GPO Firewall Considerations or use Tailscale to bypass this entirely.


4. Verify the device is shared

Find the host's IP address (needed for the client):

ipconfig
# Note the IPv4 Address of the relevant network adapter
# If using Tailscale, use the Tailscale IP (100.x.x.x) instead

Final confirmation:

usbipd list
# STATE column should read: Shared

The host is fully configured.


Client Setup (Remote machine)

All commands below must be run in an elevated (Administrator) PowerShell.

1. Install usbip-win2

Download the latest release installer from the usbip-win2 releases page.

Run the installer. It installs a kernel-mode driver, so a reboot is required after installation.

After rebooting, verify:

usbip --version

2. Test connectivity before attaching

Before attempting to attach, verify port 3240 is reachable from the client:

Test-NetConnection -ComputerName <HOST-IP> -Port 3240

3. Attach the remote device

Replace <HOST-IP> with the host's IP address and <BUSID> with the bus ID shown on the host (e.g. 2-9):

usbip attach -r <HOST-IP> -b <BUSID>

Example:

usbip attach -r 192.168.1.100 -b 2-9

If using Tailscale, use the Tailscale IP of the host instead:

usbip attach -r 100.x.x.x -b 2-9

4. Verify the device appears

# List attached USB/IP devices
usbip port

The USB device should appear in Device Manager exactly as it would if physically plugged in. Your licensed software should detect it normally.


Using Tailscale (Recommended for cross-network or domain scenarios)

Why Tailscale simplifies this

Tailscale is a zero-config VPN built on WireGuard. Installing it on both machines creates a private encrypted network between them regardless of what physical network or domain they are on. This solves several problems at once:

  • Bypasses GPO firewall restrictions — Tailscale creates its own virtual network adapter. Corporate GPO manages the Domain/Private/Public firewall profiles but does not control the Tailscale interface, so port 3240 can be opened on that interface without any GPO conflict.
  • Works across different networks — host on a corporate LAN, client on home Wi-Fi, or anywhere else — Tailscale handles the routing transparently.
  • Stable IP addresses — each machine gets a permanent Tailscale IP (in the 100.x.x.x range) that never changes, so you never have to update connection settings when the LAN IP changes.
  • Encrypted by default — all traffic between machines is encrypted via WireGuard.
  • No credentials stored on the client — Tailscale uses OAuth via your identity provider. No passwords are stored on the machine. Access is controlled via pre-auth keys and device approval (see below).
  • Free for personal use — Tailscale's free tier supports up to 100 devices.

Setup

On both machines — install Tailscale:

winget install Tailscale.Tailscale

Or download from https://tailscale.com/download.

On the host machine — sign in with your account:

After installation, Tailscale opens a browser window. Sign in with Google, Microsoft, or GitHub. This is your account — the one you control.

On the client machine — do not sign in directly. Instead, use a pre-auth key (see below).


Securely onboarding the remote user

The remote user on the client machine should never sign in with your identity provider credentials. Instead, use Tailscale's pre-auth key system combined with device approval so you retain full control at every step.

Step 1 — Enable device approval

In the Tailscale admin panel, require your explicit approval before any new device can join your network:

  1. Go to https://login.tailscale.com/admin/machines
  2. Click SettingsDevice approval
  3. Enable Require approval for new devices

With this enabled, any new machine that tries to join sits in a pending state until you approve it. You receive an email notification and nothing connects until you click approve.

Step 2 — Generate a pre-auth key

A pre-auth key lets the remote user join your tailnet without needing your identity provider credentials.

  1. Go to https://login.tailscale.com/admin/settings/keys
  2. Click Generate auth key
  3. Configure it:
    • One-time use — key expires after first use, cannot be reused
    • Expiry — set a short window (e.g. 1–4 hours) so it's useless if intercepted
    • Tags — optionally tag the device (e.g. tag:usb-client) for ACL control later
  4. Copy the key and send it to the remote user securely (Teams, Signal, etc.)

Step 3 — Remote user joins using the key

On the client machine, instead of clicking "Sign in", the remote user opens a command prompt and runs:

tailscale up --authkey=<PASTE-KEY-HERE>

The machine joins your tailnet in a pending state — it cannot communicate with anything yet.

Step 4 — You approve the device

You receive an email notification. Go to the admin panel, review the new device, and click Approve. Only after your approval does the device become active on your tailnet.

Step 5 — Verify the connection

# On the host
tailscale status
# The client machine should appear in the list with its 100.x.x.x IP
# On the client
tailscale status
ping 100.x.x.x    # host's Tailscale IP

Restricting access with ACLs

By default, all devices on a tailnet can reach each other. You can lock this down so the client machine can only reach the host on port 3240 and nothing else.

In the Tailscale admin panel, go to Access Controls and edit the ACL policy:

{
  "tagOwners": {
    "tag:usb-host":   ["autogroup:owner"],
    "tag:usb-client": ["autogroup:owner"]
  },
  "acls": [
    {
      "action": "accept",
      "src":    ["tag:usb-client"],
      "dst":    ["tag:usb-host:3240"]
    }
  ]
}

This allows the client to reach the host on port 3240 only — nothing else on your tailnet is accessible from the client machine.

To apply tags, go to the admin panel → Machines → click the machine → Edit tags.


Tailscale + usbipd firewall rule

Even with Tailscale, you still need a local firewall rule on the host. Scope it exclusively to the Tailscale interface rather than the corporate LAN — this avoids GPO interference and limits exposure:

New-NetFirewallRule `
  -DisplayName "usbipd USB/IP (Tailscale)" `
  -Direction Inbound `
  -Protocol TCP `
  -LocalPort 3240 `
  -Action Allow `
  -InterfaceAlias "Tailscale"

Then attach from the client using the host's Tailscale IP:

usbip attach -r 100.x.x.x -b 2-9

Domain-Joined Host: GPO Firewall Considerations

If the host machine is joined to a corporate domain and Tailscale is not an option, Group Policy may manage the Windows Firewall and block inbound connections even when a local allow rule exists.

Diagnosing GPO interference

Step 1 — Check effective firewall policy:

netsh advfirewall show allprofiles

If you see LocalFirewallRules: N/A (GPO-store only) under any profile, GPO has taken full control and local rules are completely ignored — any rule you add via PowerShell or the firewall UI will have no effect.

Step 2 — Check the full GPO report:

gpresult /h "$env:USERPROFILE\gpo-report.html"
Start-Process "$env:USERPROFILE\gpo-report.html"

Look for the Windows Firewall with Advanced Security section. Key warning signs:

  • Inbound connections: Block under Domain/Private/Public profile settings
  • Apply local firewall rules: Not Configured

Step 3 — Test from the client:

Test-NetConnection -ComputerName <HOST-IP> -Port 3240

If TcpTestSucceeded : False, use Tailscale or one of the options below.


Option A: Ask IT to allow local firewall rules

Ask your domain administrator to make one of the following changes via GPO for your machine or OU:

  • Set Apply local firewall rules to Enabled for the Domain profile in the Windows Firewall policy, or
  • Add an explicit GPO inbound rule allowing TCP 3240 for your machine

This is a minimal change, easily justified for a developer workstation.


Option B: SSH tunnel workaround

If GPO cannot be changed and Tailscale is not permitted, tunnel usbipd traffic through SSH on port 22, which is typically allowed in corporate environments.

On the host — enable OpenSSH Server:

# Check if already installed
Get-WindowsCapability -Online -Name OpenSSH.Server*

# Install if not present
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0

# Start and set to automatic
Start-Service sshd
Set-Service -Name sshd -StartupType Automatic

Verify SSH is listening:

netstat -an | findstr :22

On the client — create the SSH tunnel:

Open a PowerShell window and run:

ssh -L 3240:localhost:3240 <USERNAME>@<HOST-IP>

Keep this window open — the tunnel stays active as long as this session is running.

On the client — attach via localhost:

Open a second elevated PowerShell window:

usbip attach -r localhost -b <BUSID>

To make the SSH tunnel persistent, create a scheduled task that re-establishes the tunnel at logon:

$action = New-ScheduledTaskAction -Execute "ssh.exe" `
  -Argument "-N -L 3240:localhost:3240 <USERNAME>@<HOST-IP>"
$trigger = New-ScheduledTaskTrigger -AtLogOn
$principal = New-ScheduledTaskPrincipal -UserId "$env:USERNAME" -RunLevel Highest
Register-ScheduledTask -TaskName "USB-IP SSH Tunnel" `
  -Action $action -Trigger $trigger -Principal $principal

Detaching and Cleanup

On the client — detach the device

List attached ports:

usbip port

Example output:

Port 00: <Port in Use> at Full Speed(12Mbps)
       : unknown vendor : unknown product (0781:5571)
       2-9 -> usbip://192.168.1.100:3240/2-9

Detach using the port number shown:

usbip detach -p 00

If using the SSH tunnel, you can now close the tunnel session.

On the host — unbind the device (optional)

To stop sharing the device permanently:

usbipd unbind --busid 2-9

To remove the firewall rule:

Remove-NetFirewallRule -DisplayName "usbipd USB/IP"
# Or if using Tailscale-scoped rule:
Remove-NetFirewallRule -DisplayName "usbipd USB/IP (Tailscale)"

Revoking Tailscale access

To immediately cut off the client machine from your tailnet:

  1. Go to https://login.tailscale.com/admin/machines
  2. Find the client machine
  3. Click Delete or Disable — access is revoked instantly

Reconnecting After Reboot

Host side

Nothing to do. The usbipd service and Tailscale both start automatically. The device will be in Shared state after every reboot as long as it is physically plugged in.

Client side — direct or Tailscale connection

The attach does not persist across reboots. Automate with a scheduled task:

# Replace IP with Tailscale IP (100.x.x.x) if using Tailscale
$action = New-ScheduledTaskAction -Execute "powershell.exe" `
  -Argument "-NonInteractive -WindowStyle Hidden -Command `"usbip attach -r 100.x.x.x -b 2-9`""
$trigger = New-ScheduledTaskTrigger -AtLogOn
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -RunLevel Highest
Register-ScheduledTask -TaskName "USB-IP Auto Attach" `
  -Action $action -Trigger $trigger -Principal $principal

Client side — SSH tunnel

The tunnel also needs to be re-established after reboot. Use the scheduled task from the SSH tunnel section above, then add a second task for the attach that runs a few seconds later:

$action = New-ScheduledTaskAction -Execute "powershell.exe" `
  -Argument "-NonInteractive -WindowStyle Hidden -Command `"Start-Sleep 5; usbip attach -r localhost -b 2-9`""
$trigger = New-ScheduledTaskTrigger -AtLogOn
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -RunLevel Highest
Register-ScheduledTask -TaskName "USB-IP Auto Attach (SSH)" `
  -Action $action -Trigger $trigger -Principal $principal

Troubleshooting

usbipd not recognized after install

Close and reopen PowerShell. If still not found:

$env:PATH += ";C:\Program Files\usbipd-win"

Device shows "Not shared" after bind

Try unbinding and rebinding:

usbipd unbind --busid 2-9
usbipd bind --busid 2-9
usbipd list

Test-NetConnection returns False on port 3240

  • Check if GPO is blocking local rules: netsh advfirewall show allprofiles
  • If LocalFirewallRules: N/A (GPO-store only) appears, local rules are ignored — use Tailscale or the SSH tunnel.
  • Simplest fix: install Tailscale on both machines and use the Tailscale IP.

Tailscale machines can't see each other

  • Confirm both machines are in your tailnet: https://login.tailscale.com/admin/machines
  • Confirm the client device has been approved if device approval is enabled
  • Try ping 100.x.x.x from the client to the host's Tailscale IP
  • Run tailscale status on both machines to confirm they show as connected

Tailscale pre-auth key not working

  • Check the key has not expired (set expiry when generating)
  • Check the key has not already been used (one-time keys expire after first use)
  • Generate a new key from the admin panel

Client cannot connect despite port appearing open

  • Confirm the usbipd service is running on the host:
    Get-Service -Name usbipd
    Start-Service -Name usbipd  # if stopped
  • Confirm the device is still in Shared state:
    usbipd list

usbip attach fails or device not visible in Device Manager

  • Confirm the reboot after usbip-win2 installation was completed
  • Try detaching and reattaching:
    usbip detach -p 00
    usbip attach -r <HOST-IP> -b <BUSID>
  • Check Windows Event Viewer under Windows Logs → System for driver errors

License software does not detect the dongle

Some license systems fingerprint the USB connection and can detect virtualization. This is rare but possible. If it occurs, a hardware KVM switch or physical USB extender cable may be the only alternative.

Also ensure the device is not mounted on the host (e.g. as a drive letter) while the client is using it — eject it on the host before the client attaches.

Device disconnects intermittently

Disable USB selective suspend on the host:

powercfg /setacvalueindex SCHEME_CURRENT 2a737441-1930-4402-8d77-b2bebba308a3 48e6b7a6-50f5-4782-a5d4-53bb8f07e226 0
powercfg /setactive SCHEME_CURRENT

Also ensure the host machine does not sleep while the client is using the device.


Security Considerations

  • usbipd-win has no built-in authentication. Always restrict who can connect using one of these approaches:

    • Tailscale with device approval and ACLs (most secure)
    • Firewall rule scoped to the client's specific IP on a LAN
    • SSH tunnel with key-based authentication
  • Tailscale authentication model:

    • No passwords are stored on the client machine — Tailscale uses OAuth via your identity provider
    • The node key issued to a machine is useless without the identity provider session behind it
    • Use pre-auth keys with short expiry and one-time use for onboarding
    • Enable device approval so no machine joins without your explicit sign-off
    • Use ACLs to restrict the client to port 3240 on the host only
    • Revoke access instantly from the admin panel at any time
  • SSH tunnel traffic is encrypted but requires SSH credentials — a good fallback when Tailscale is not available.

  • Do not expose port 3240 on a public or untrusted network without Tailscale or a VPN.

  • The usbipd service runs as SYSTEM. Keep the software updated.


Quick Reference Cheatsheet

Host (usbipd-win)

# Install
winget install usbipd

# List devices
usbipd list

# Bind (share) a device — run once, persists across reboots
usbipd bind --busid <BUSID>

# Unbind (stop sharing)
usbipd unbind --busid <BUSID>

# Open firewall — LAN (all profiles)
New-NetFirewallRule -DisplayName "usbipd USB/IP" -Direction Inbound `
  -Protocol TCP -LocalPort 3240 -Action Allow -Profile Domain,Private,Public

# Open firewall — Tailscale interface only (recommended)
New-NetFirewallRule -DisplayName "usbipd USB/IP (Tailscale)" -Direction Inbound `
  -Protocol TCP -LocalPort 3240 -Action Allow -InterfaceAlias "Tailscale"

# Check if GPO is overriding local firewall rules
netsh advfirewall show allprofiles

# Get Tailscale IP
tailscale ip

# Check Tailscale connection status
tailscale status

# Check usbipd service status
Get-Service -Name usbipd

# Enable OpenSSH Server (SSH tunnel fallback)
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
Start-Service sshd
Set-Service -Name sshd -StartupType Automatic

Client (usbip-win2)

# Install: download from https://github.com/vadimgrn/usbip-win2/releases
# Reboot after install

# Join tailnet using pre-auth key (no credentials needed)
tailscale up --authkey=<KEY-FROM-ADMIN-PANEL>

# Test connectivity
Test-NetConnection -ComputerName <HOST-IP> -Port 3240        # LAN
Test-NetConnection -ComputerName 100.x.x.x -Port 3240        # Tailscale

# Attach device (direct or Tailscale)
usbip attach -r <HOST-IP> -b <BUSID>
usbip attach -r 100.x.x.x -b <BUSID>                         # Tailscale

# SSH tunnel (if port 3240 is blocked and no Tailscale)
ssh -L 3240:localhost:3240 <USERNAME>@<HOST-IP>               # keep open
usbip attach -r localhost -b <BUSID>                          # second window

# List attached ports
usbip port

# Detach device
usbip detach -p <PORT>

Tailscale admin panel

Task URL
View all machines https://login.tailscale.com/admin/machines
Generate pre-auth key https://login.tailscale.com/admin/settings/keys
Edit ACL policy https://login.tailscale.com/admin/acls
Device approval settings https://login.tailscale.com/admin/settings/devices

References

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment