Expose from CLI
Updated
The netbird expose command lets peers expose local services to the public internet through the NetBird reverse proxy without using the dashboard. Services created this way are ephemeral: the command keeps them alive through periodic renewals and requests their removal when it stops. If that request fails, the management server removes the service after its session expires.
This supports both HTTP and L4 (TCP, UDP, TLS) protocols, making it useful for quick demos, temporary access to development servers, webhook receivers, database tunnels, or sharing local work with teammates.
Prerequisites
Before using netbird expose, make sure:
- The NetBird client is connected — run
netbird upfirst. - The Peer Expose feature is enabled by your account administrator (see Enable peer expose below).
- If peer group restrictions are configured, your peer must be in one of the allowed groups.
netbird expose requires inbound connections: if Block Inbound Connections is enabled on the peer, the command fails until you disable that setting.
Enable peer expose
An account administrator must enable peer expose before any peer can use the netbird expose command.
From the dashboard
- Navigate to Settings > Clients in the NetBird dashboard.
- Scroll to the Peer Expose section.
- Toggle Enable Peer Expose on.
- Select specific peer groups that are allowed to expose services. Groups without proper permission will get a
PermissionDeniederror. - Click Save Changes.
From the API
Set peer_expose_enabled to true in the account settings:
curl -X PUT "https://api.netbird.io/api/accounts/{account_id}" \
-H "Authorization: Token {api_token}" \
-H "Content-Type: application/json" \
-d '{
"settings": {
"peer_expose_enabled": true,
"peer_expose_groups": ["ch8i4ug6lnn4g9hqv7m0"]
}
}'
Set peer_expose_groups to an array of peer group IDs to restrict which peers can expose services.
Basic usage
Expose a local HTTP server running on a specific port:
netbird expose 8080
On success, the command outputs the public URL and keeps the session alive:
Service exposed successfully!
Name: myapp-a1b2c3
URL: https://myapp-a1b2c3.proxy.example.com
Domain: myapp-a1b2c3.proxy.example.com
Protocol: http
Internal: 8080
Press Ctrl+C to stop exposing.
The service is now accessible at the displayed URL. Press Ctrl+C to stop exposing and remove the service.
Exposing L4 services
To expose a TCP, UDP, or TLS service, use the --protocol flag:
netbird expose --protocol tcp 5432
For L4 services, the output includes the external port on the proxy cluster:
Service exposed successfully!
Name: myapp-a1b2c3
URL: myapp-a1b2c3.proxy.example.com:5432
Protocol: tcp
Internal: 5432
External: 5432
Press Ctrl+C to stop exposing.
By default, the external port matches the target port. Use --with-external-port to specify a different one:
netbird expose --protocol tcp --with-external-port 15432 5432
If the proxy cluster does not support custom ports (e.g., NetBird shared cloud clusters), the port is auto-assigned. When you explicitly specify --with-external-port but the cluster overrides your choice, the output indicates the reassignment:
External: 38721
Note: requested port 15432 was reassigned
If you do not specify --with-external-port, the assigned port is shown without a reassignment note.
Command reference
netbird expose <port> [flags]
Arguments
| Argument | Required | Description |
|---|---|---|
port | Yes | The local port number to expose (1–65535) |
Flags
| Flag | Type | Default | Description |
|---|---|---|---|
--protocol | string | http | Protocol to use: http, https, tcp, udp, or tls. |
--with-external-port | uint16 | target port | Public-facing port on the proxy cluster. L4 only (TCP, UDP, TLS). Defaults to the target port. |
--with-pin | string | Protect the exposed service with a 6-digit PIN. HTTP only. | |
--with-password | string | Protect the exposed service with a password. HTTP only. | |
--with-user-groups | strings | Restrict access to specific user groups via SSO. HTTP only. | |
--with-custom-domain | string | Custom domain for the exposed service, must be configured in your account. | |
--with-name-prefix | string | Prefix for the generated service name (e.g. --with-name-prefix my-app). |
Authentication flags (--with-pin, --with-password, --with-user-groups) are only supported for HTTP/HTTPS protocols. L4 protocols (TCP, UDP, TLS) do not support these flags because there is no HTTP layer to serve a login page. Similarly, --with-external-port is only supported for L4 protocols.
Authentication
By default, exposed services are publicly accessible to anyone who knows the URL. You can protect them using one or more authentication methods.
PIN protection
Protect the service with a 6-digit PIN that users must enter before accessing it:
netbird expose 8080 --with-pin 123456
Password protection
Protect the service with a password:
netbird expose 8080 --with-password mysecretpassword
SSO user group restriction
Restrict access to users in specific groups from your identity provider:
netbird expose 8080 --with-user-groups engineering,devops
Users must authenticate through your configured IdP (OIDC) and belong to one of the specified groups to access the service.
Combining authentication methods
You can combine multiple authentication methods. When multiple methods are enabled, users can choose which one to use:
netbird expose 8080 --with-pin 123456 --with-password backup-password --with-user-groups admin
If no authentication flag is provided, the service is publicly accessible without any protection. Consider always using at least one authentication method for sensitive services.
For more details on how each authentication method works, see Reverse Proxy Authentication.
Custom domains
By default, netbird expose generates a random subdomain on your proxy cluster domain (e.g., a1b2c3d4e5f6.proxy.example.com). You can customize this in two ways.
Name prefix
Use --with-name-prefix to add a readable prefix to the generated subdomain:
netbird expose 8080 --with-name-prefix myapp
This produces a domain like myapp-a1b2c3.proxy.example.com instead of a fully random one.
The prefix must follow these rules:
- Lowercase letters, numbers, and hyphens only
- Must start and end with a letter or number
- 1 to 32 characters long
Custom domain
Use --with-custom-domain to expose the service on a domain you own:
netbird expose 8080 --with-custom-domain app.example.com
The custom domain must already be configured and verified in your account before it can be used with the expose command. See Custom Domains for setup instructions.
Session lifecycle
How sessions work
When you run netbird expose, the following happens:
- The CLI sends the request to the local NetBird daemon.
- The daemon connects to the management server and creates an ephemeral reverse proxy service.
- The management server assigns the service domain and sends its configuration to the proxy cluster. For HTTP/HTTPS services, the proxy handles the public TLS certificate.
- The daemon returns the service URL to the CLI, which displays it to you.
- The daemon enters a renewal loop, sending a keep-alive signal to the management server every 30 seconds.
- The management server stores the service and its last renewal time in the database. Each renewal resets the 90-second TTL.
The management server checks for expired sessions every 30 seconds. With management running normally, cleanup usually happens between 90 and 120 seconds after the last successful renewal. Cleanup can take longer if management is unavailable or has a backlog of expired services.
Stopping a session
There are several ways a session can end:
| Trigger | What happens | Service removed? |
|---|---|---|
| Ctrl+C or SIGTERM | The daemon attempts a stop request with a 10-second timeout. | When the request succeeds; otherwise, after expiration and cleanup |
| Renewal failure | The renewal loop exits with an error and the daemon attempts a stop request. | When the stop request succeeds; otherwise, after expiration and cleanup |
| Daemon stops unexpectedly | The daemon process is killed (e.g., kill -9, system crash). No stop request is sent. | After expiration and cleanup |
| Session TTL expires | The management server's cleanup process deletes services whose last renewal is more than 90 seconds old. | On a subsequent cleanup pass |
| Management server restart | The service and last renewal time remain in the database. | Only if explicitly stopped or no longer renewed before cleanup |
What causes expiration
A session expires when the management server does not receive a renewal within 90 seconds. Common causes:
- Network interruption between the peer and the management server.
- Client process killed without graceful shutdown (e.g.,
kill -9, OOM kill, system reboot). - Daemon restarted while an expose session is active.
- Management server unavailable long enough to interrupt renewals. A restart does not itself delete sessions, but a failed renewal ends the client's renewal loop.
When a session expires, the management server automatically deletes the ephemeral service and logs a "Peer expose expired" activity event.
The TTL is not a guarantee that access ends within 90 seconds. Until cleanup removes the service, the proxy may continue routing requests if the backend is still reachable. Use authentication for sensitive services and stop sessions gracefully when possible.
Rate limits
Each peer can have a maximum of 10 active expose sessions at the same time. If you need to expose more services simultaneously, either stop an existing session first or use the dashboard to create permanent services.
Attempting to exceed this limit returns an error:
peer has reached the maximum number of active expose sessions (10)
Permissions
The netbird expose command requires permission at two levels:
- Account level — The Peer Expose feature must be enabled in account settings.
- Group level — If
peer_expose_groupsis configured, the peer must belong to at least one of the specified groups.
If the peer does not have permission, the command returns a "permission denied" error.
Comparison with dashboard services
| Property | Dashboard services | CLI expose services |
|---|---|---|
| Created via | Dashboard or API | netbird expose command |
| Lifecycle | Permanent until manually deleted | Ephemeral: removed by a successful stop request or expiration cleanup |
| TTL | None | 90 seconds, renewed every 30 seconds; cleanup runs every 30 seconds |
| Service modes | HTTP, TCP, UDP, TLS | HTTP, HTTPS, TCP, UDP, TLS |
| Multiple targets | Yes (path-based routing for HTTP) | Single target (the local port) |
| Custom domains | Yes | Yes (if pre-configured) |
| Authentication | SSO, Password, PIN, Header Auth, Access Restrictions | SSO (user groups), Password, PIN (HTTP only) |
| Advanced settings | Host header, redirect rewriting, PROXY protocol, session timeout | Not available |
| Visibility | Always shown in dashboard | Shown while active |
| Rate limit | None | Max 10 per peer |
| Source label | Permanent | Ephemeral |
Activity tracking
All peer expose actions are recorded in the activity log and visible in the dashboard under Events > Audit:
| Event | Description |
|---|---|
| Peer exposed service | A peer created an expose session. Includes peer name, domain, and whether authentication is enabled. |
| Peer unexposed service | A peer stopped an expose session (Ctrl+C or explicit stop). |
| Peer expose expired | An expose session was removed because the renewal TTL expired. |
Troubleshooting
"client is not running, run 'netbird up' first"
The NetBird daemon is not connected. Start it with:
netbird up
"permission denied"
Either peer expose is disabled in account settings, or your peer is not in an allowed group. Ask your account administrator to:
- Enable Peer Expose in Settings > Clients.
- Add your peer's group to the allowed peer groups (or leave the group list empty to allow all peers).
"peer has reached the maximum number of active expose sessions (10)"
You have 10 active expose sessions on this peer. Stop one of them before creating a new one. Sessions that are no longer renewed expire 90 seconds after their last successful renewal and are removed on a subsequent cleanup pass, normally within another 30 seconds.
"generate service name: invalid name prefix"
The --with-name-prefix value contains invalid characters. The prefix must be:
- Lowercase letters (
a-z), numbers (0-9), and hyphens (-) only - Must start and end with a letter or number
- 1 to 32 characters long
"unsupported protocol"
The --protocol flag accepts http, https, tcp, udp, or tls. Any other value is rejected.
Service URL returns connection error after Ctrl+C
This is expected after the stop request succeeds and the proxy receives the removal. The service's routing configuration is removed, but the wildcard DNS record is unchanged, so the hostname can still resolve to the proxy. DNS resolution alone does not mean the exposed service is still available.
Service still accessible after client crash
If renewals stop without a successful stop request, the service remains configured until the management server removes it. This normally takes 90–120 seconds from the last successful renewal. Requests can still succeed during that window if the backend remains reachable. A management outage or cleanup backlog can delay removal further.
Examples
Quick demo server
Expose a local development server for a quick demo:
# Start your dev server
npm run dev # Runs on port 3000
# In another terminal, expose it
netbird expose 3000 --with-name-prefix demo
Protected staging environment
Expose a staging server with password protection:
netbird expose 8080 --with-password staging-review --with-name-prefix staging
Team-only access
Expose a service restricted to your engineering team via SSO:
netbird expose 8080 --with-user-groups engineering --with-name-prefix internal-api
Webhook receiver
Temporarily expose a local webhook endpoint for testing:
netbird expose 9000 --with-name-prefix webhooks
Database tunnel
Expose a local PostgreSQL instance over TCP:
netbird expose --protocol tcp 5432
Game server
Expose a local UDP game server on a specific external port:
netbird expose --protocol udp --with-external-port 27015 27015
TLS passthrough
Expose a service that handles its own TLS termination:
netbird expose --protocol tls --with-custom-domain tls.example.com 4443
Related pages
- Reverse Proxy - overview of the reverse proxy feature, dashboard setup, and concepts
- Authentication - SSO, password, PIN, and header authentication, plus access restrictions
- Custom Domains - configure your own domain names
- Access Logs - monitor traffic to your reverse proxy services
- CLI Reference -
netbird exposecommand reference

