> For the complete documentation index, see [llms.txt](https://docs.roboflow.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.roboflow.com/deployment/self-hosted/inference-server/configuration/security.md).

# Securing a Self-Hosted Server

Keep self-hosted Inference local by default, choose who can run Workflows, and configure model, media, and transport controls.

Self-hosted Inference keeps local development permissive: Custom Python runs locally by default, and the server does not require an API key unless you configure authentication. The CLI publishes the server on `127.0.0.1` by default, with the exceptions below, so other machines cannot reach that published port.

For upgrade behavior and version scope, see [Security Configuration Migration](/deployment/self-hosted/inference-server/configuration/security-migration.md).

## Restrict network access

Choose how clients reach your server before changing which Workflow features they can use.

<table data-search="false"><thead><tr><th>Launch method</th><th>Default network access</th><th>Configuration</th></tr></thead><tbody><tr><td>Inference CLI on CPU or GPU hosts</td><td>Host loopback, 127.0.0.1:9001</td><td>Use --bind-address or -b to select another host address.</td></tr><tr><td>Inference CLI with a Jetson image</td><td>All host interfaces, 0.0.0.0:9001</td><td>Use --bind-address 127.0.0.1 for local-only access.</td></tr><tr><td>macOS and Windows desktop bundles</td><td>127.0.0.1</td><td>Set HOST explicitly to select another interface.</td></tr><tr><td>Manual Docker or Compose</td><td>Determined by the port mapping</td><td>Use 127.0.0.1:9001:9001 for local-only access.</td></tr><tr><td>Inference CLI with --tunnel</td><td>The CLI publishes on 0.0.0.0 so the tunnel can connect.</td><td>Review both LAN and tunnel access.</td></tr></tbody></table>

### Starting the server with the CLI

`inference server start` publishes port 9001 on `127.0.0.1`, so a server started this way answers only processes on the machine that started it. That is the right default for local development, and it is the only configuration in which the permissive defaults on this page are safe. The CLI warns when publishing on a non-loopback address.

```bash
# default: reachable only from this machine
inference server start

# deliberate LAN access: reachable from every machine that can route to this host - secure it first
inference server start --bind-address 0.0.0.0 --env-file inference.env
```

{% hint style="warning" %}
**NVIDIA Jetson is the exception.** Jetson images are headless edge devices that are almost always driven from another machine, so `inference server start` publishes them on `0.0.0.0` - reachable from anything that can route to the device, including the rest of the Wi-Fi network it is on. Apply the controls below, or pin it down with `inference server start --bind-address 127.0.0.1`.
{% endhint %}

### Starting the server with `docker run`

The image cannot make this choice for you, so **you have to make it on the command line**. `HOST` selects the interface *inside* the container, and the container has its own network namespace: an image that shipped `HOST=127.0.0.1` would bind the container's own loopback, which published ports cannot reach, and the server would be unreachable even from the host. This is why every image ships `HOST=0.0.0.0`. Keep it that way inside a bridge-networked container and put the restriction on the host side of the port mapping instead:

```bash
# reachable only from this machine
docker run --rm -p 127.0.0.1:9001:9001 roboflow/roboflow-inference-server-cpu:latest

# reachable from every machine that can route to this host - secure it first
docker run --rm -p 9001:9001 roboflow/roboflow-inference-server-cpu:latest
```

Two cases behave differently:

* **`--network host`** (Linux) removes the port mapping entirely, so the container binds host interfaces directly. There, `-e HOST=127.0.0.1` *is* the way to keep the server local.
* **Kubernetes** gives each pod its own network namespace, so keep `HOST=0.0.0.0` and control exposure with the Service type and NetworkPolicy rather than the bind address.

{% hint style="warning" %}
**`-p 9001:9001` means every interface.** Without an explicit address, Docker publishes on `0.0.0.0`, including any public IP the host has. On Linux, Docker's own forwarding rules are evaluated before `ufw`/`firewalld` rules in most default setups, so a host firewall you configured separately may not block it. Check what is actually listening with `docker port <container>` or `ss -tlnp | grep 9001`.
{% endhint %}

{% hint style="info" %}
Direct Python server launches, host networking, and manual Docker publishing need their own network configuration; the CLI default does not restrict those paths.
{% endhint %}

Binding to anything other than loopback means untrusted clients may reach the server, and the defaults described on this page are not written for that. Work through [Before allowing remote clients](#before-allowing-remote-clients) first.

## Enforce authentication

Set `WORKSPACES_WHITELISTED_FOR_LOCAL_DEPLOYMENT` to the allowed Workspace slugs to require an API key for inference, Workflow, and integrated `/inference_pipelines/*` requests. Clients can send the key in `Authorization: Bearer YOUR_API_KEY`, the `api_key` query parameter, or a supported JSON request body; the server verifies Workspace membership through the Roboflow API.

For example, place this in the `inference.env` file passed to the CLI:

```dotenv
WORKSPACES_WHITELISTED_FOR_LOCAL_DEPLOYMENT=your-workspace-url-slug
```

If you use a different identity system or need authentication without Roboflow API connectivity, put an authenticated proxy in front of the server and prevent clients from bypassing it.

### Health and observability

The Workspace check leaves `/`, `/docs`, `/redoc`, `/info`, `/healthz`, `/readiness`, `/metrics`, `/openapi.json`, static assets, and the opt-in `/secure-gateway/health` endpoint available without an API key. This preserves health checks and [telemetry](/deployment/self-hosted/inference-server/configuration/telemetry.md); limit network access if their output is sensitive. TLS client-certificate requirements, when enabled, still apply to these endpoints.

## Keep Custom Python available to trusted callers

`ALLOW_CUSTOM_PYTHON_EXECUTION_IN_WORKFLOWS=True` and `WORKFLOWS_CUSTOM_PYTHON_EXECUTION_MODE=local` remain the defaults, so local users can run Custom Python without opting in. That code runs in the server process with its permissions: authentication controls who can submit it, but does not sandbox an authenticated caller. The server logs a security notice when local Custom Python is enabled without Workspace authentication.

### Disable local execution when it is not needed

Set `ALLOW_CUSTOM_PYTHON_EXECUTION_IN_WORKFLOWS=False` to reject local Custom Python execution. Modal execution is a separate mode and is not disabled by this flag; see [Custom Blocks](https://docs.roboflow.com/workflows/blocks/custom-blocks) and the [offline migration guidance](/deployment/self-hosted/inference-server/configuration/security-migration.md#offline-custom-python).

## Protect model access

[Model Package Security](/deployment/self-hosted/inference-server/configuration/model-security.md) distinguishes Workspace authentication, per-model authorization, and permission to load executable packages. In particular, online per-model authorization cannot be combined with direct local package loading because a filesystem path has no Roboflow model identity to authorize.

## Configure video workloads

[Video Configuration](/deployment/self-hosted/inference-server/configuration/video-configuration.md) covers the managed pipeline process limit, warm workers, and server-side media references. A process cap bounds resource use; it does not authenticate callers or isolate their code.

## Protect network transport

Use [HTTPS](/deployment/self-hosted/inference-server/configuration/https.md) at the server or an authenticated TLS proxy for traffic beyond the host. Outbound [Secure Gateway](/deployment/self-hosted/enterprise/secure-gateway.md#connecting-inference-servers) connections have their own TLS configuration, which is separate from the server's listening address.

## Restrict image fetching

A caller-supplied image URL can make the server contact internal services or metadata endpoints. [Accepted Input Formats](/deployment/self-hosted/inference-server/configuration/input-formats.md#sending-urls-to-inference-images) explains how to disable URL input, restrict destinations, and validate redirects while keeping the formats your application needs. These image-fetching controls do not replace network restrictions for other Workflow integrations or video transports.

## Restrict Webhook Sink destinations

The Webhook Sink can send Workflow data to services on private networks. Self-hosted Inference permits these destinations by default so existing factory and LAN webhooks continue to work.

Set `ALLOW_WEBHOOK_WORKFLOWS_SINK_TO_NON_GLOBAL_ADDRESSES=False` when callers are not trusted to choose destinations. This rejects loopback, private, link-local (including cloud metadata), CGNAT, reserved, and multicast addresses. Webhook requests resolve hostnames once and connect to the validated address. Redirects and HTTP(S) proxies are rejected for either setting.

## Before allowing remote clients

Binding to anything other than loopback means untrusted clients may reach the server, and the permissive defaults on this page are not written for that. Before you expose the port, at minimum:

* Choose the bind address deliberately: `inference server start` without `--bind-address`, or `-p 127.0.0.1:9001:9001` with `docker run`, unless you need remote clients.
* Keep access limited to known clients: stay on a private network or VPC and reach the server through a VPN, SSH tunnel, or service mesh rather than a public IP, and use host and cloud firewalls or security groups to allow port `9001` only from the specific clients that need it.
* Enable Workspace authentication or your own authentication at the proxy. Without it, anyone who can reach port `9001` can run models and Workflows on your hardware, on your Roboflow account's quota.
* Configure TLS for traffic that crosses an untrusted network.
* Keep Custom Python and executable model packages available only to callers you trust with the server's permissions. Left enabled on a reachable server, local Custom Python is remote code execution for anyone who can send a Workflow.
* Disable URL image input, or harden it with a destination allow-list and redirect validation, as described in [Accepted Input Formats](/deployment/self-hosted/inference-server/configuration/input-formats.md#sending-urls-to-inference-images).
* Set `ALLOW_WEBHOOK_WORKFLOWS_SINK_TO_NON_GLOBAL_ADDRESSES=False` unless callers must send webhooks to trusted private or local services.
* Put a reverse proxy in front of the server (nginx, Traefik, Caddy, or a cloud load balancer) if you need to expose it more broadly; that gives you a single place to add TLS, rate limiting, and access logging.
* Verify that your health checks and metrics collector can still connect.

Never publish the inference port directly to the public internet without authentication and TLS in place.

Use the [Production Readiness Checklist](/deployment/production-checklist.md) for retries, capacity, and monitoring.
