# Networking
URL: https://metorial.com/docs/build/sandboxes/networking

Expose ports with access tokens or publicly, and restrict outbound traffic with firewalls.

---

Servers inside a machine are reachable over HTTPS, either with a short-lived access token or publicly. Outbound traffic is open by default, and firewalls let you limit it to the addresses you allow.

<PageIntro>
  <Learn>
    * How to reach a port with an access token
    * How to make a port public
    * How to restrict outbound traffic with firewalls
  </Learn>

  <Reading title="Before you start">
    * A running machine with a server listening on a port
  </Reading>
</PageIntro>

## Reach a port [#reach-a-port]

Every port gets its own HTTPS URL. Your server must listen on `0.0.0.0`, since servers bound to `localhost` can't receive outside traffic.

By default, a port only accepts requests that carry an access token in the `Metorial-Sandbox-Token` header. Generate a token and the URLs for the ports you need:

```bash
metorial-admin sandbox machines access ports $MACHINE --ports '[3000]' --ttl-seconds 3600
```

Use `--ports '"*"'` to get a token that works for every port.

```json
{
  "object": "sandbox.machine.access",
  "token": "…",
  "ports": [3000],
  "urls": {
    "ports": { "3000": "https://<machine-host>.metorial.run" }
  },
  "expires_at": "2026-10-16T15:00:00.000Z"
}
```

```bash
curl -H "Metorial-Sandbox-Token: $TOKEN" https://<machine-host>.metorial.run/health
```

Tokens last one hour by default and up to 24 hours. In the dashboard, open the machine and use **Generate URL** to get a link for a port.

<Tip>
  The SDK's `connect()` adds and refreshes the token for you. See [Connect with the SDK](/docs/build/sandboxes/connect-sdk#call-a-server-in-the-machine).
</Tip>

## Make a port public [#make-a-port-public]

Public ports accept requests without a token. Use them for webhooks, previews you share with people, or anything else that can't send a header.

```bash
metorial-admin sandbox machines update $MACHINE --public-ports '[3000]'
```

<CodeBlockTabs defaultValue="TypeScript" groupId="language" mode="code">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="TypeScript">
      TypeScript
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="Python">
      Python
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="TypeScript">
    ```typescript
    await metorial.sandbox.machines.update(machineId, { publicPorts: [3000] });
    ```
  </CodeBlockTab>

  <CodeBlockTab value="Python">
    ```python
    metorial.sandbox.machines.update(machine_id, public_ports=[3000])
    ```
  </CodeBlockTab>
</CodeBlockTabs>

The port keeps the same URL. Anyone with the URL can reach it, so put your own authentication in front of anything sensitive. To make the port private again, update `public_ports` without it.

## Restrict outbound traffic [#restrict-outbound-traffic]

Machines can reach any public address on the internet until you attach a firewall. A firewall bundles one or more network policies, and each policy holds rules. You can reuse the same firewall across many machines.

Once a machine has a firewall, outbound traffic is denied unless an `allow` rule matches it. When rules overlap, the one with the higher `priority` decides, so a `deny` rule can carve exceptions out of a broader `allow` rule. Private, loopback, link-local, and reserved addresses are always blocked, with or without a firewall.

<Steps>
  <Step title="Create a network policy">
    This policy allows HTTPS to GitHub's address range:

    ```bash
    metorial-admin network policies create --name allow-github --rules '[
      {
        "effect": "allow",
        "direction": "egress",
        "cidrs": ["140.82.112.0/20"],
        "ports": [{ "from": 443, "to": 443 }],
        "description": "GitHub over HTTPS"
      }
    ]'
    ```

    Save the policy ID from the output as `POLICY`.
  </Step>

  <Step title="Create a firewall">
    ```bash
    metorial-admin network firewalls create --name agent-egress --network-policy-ids $POLICY
    ```

    Save the firewall ID as `FIREWALL`. Pass several policy IDs separated by commas to combine them.
  </Step>

  <Step title="Attach it to a machine">
    ```bash
    metorial-admin sandbox machines update $MACHINE --firewall-ids $FIREWALL
    ```

    The rules apply to running machines right away, without a restart. Pass `--firewall-ids` to `machines create` to start a machine with a firewall from the first boot.
  </Step>
</Steps>

In the SDK, attach firewalls you've created with `firewallIds` (`firewall_ids` in Python) on `machines.create` or `machines.update`.

### Rule fields [#rule-fields]

| Field         | Description                                                                 |
| ------------- | --------------------------------------------------------------------------- |
| `effect`      | `allow` or `deny`                                                           |
| `direction`   | `egress` for outbound traffic. Machines ignore `ingress` rules.             |
| `cidrs`       | IP ranges in CIDR notation. Domain names aren't supported.                  |
| `ports`       | Port ranges as `{ "from": 443, "to": 443 }`. Leave out to match every port. |
| `priority`    | Higher numbers win when rules overlap                                       |
| `enabled`     | Turns a rule off without deleting it                                        |
| `description` | A note for your team                                                        |

The firewalls on a machine can open at most 98 address ranges in total, and each port range on an address counts separately. To allow all outbound traffic again, detach the firewall or add an `allow` rule for `0.0.0.0/0` and `::/0` on every port.

Firewalls belong to Network Access and also protect integrations and providers. List the firewalls on a machine with `metorial-admin network firewalls list --machine-id $MACHINE`.