---
title: "fm CLI Reference: fm tunnel Port Forwarding"
description: "Reference for the fm CLI: fm tunnel port forwarding to databases through your static IPs, the config file, exit codes, fm routes for inbound, and fm connectors."
canonical: https://fixedmark.com/docs/cli
updated: 2026-11-07
source: Fixedmark (https://fixedmark.com), BitMask LLP
---

TCP

# fm CLI reference

fm is the Fixedmark command line. fm tunnel forwards local ports through your static IPs to databases and TCP services, for drivers that have no proxy support. The other commands manage apps, IPs, routes, connectors, credentials, and keys over the REST API.

Docs preview: Fixedmark is not live yet. Endpoints are issued at launch, and these pages describe planned launch behavior. Anything marked planned may change. [Join early access](https://fixedmark.com/early-access) to get yours first.

## fm tunnel

fm tunnel listens on a local port and sends each connection to a remote host and port from your static IPs. It carries outbound traffic only.

**Terminal**

```bash
fm tunnel 5432:db.example.com:5432
```

Your driver then connects to `127.0.0.1:5432`. Give it the real server name for TLS checks, as the [databases guide](https://fixedmark.com/docs/databases) shows.

**Terminal**

```bash
# Several mappings in one process
fm tunnel 5432:db.example.com:5432 3306:mysql.example.com:3306

# Text logs instead of JSON lines
fm tunnel --log text 5432:db.example.com:5432

# A config file somewhere else
fm tunnel --config /run/secrets/fixedmark.toml 5432:db.example.com:5432
```

_fm tunnel mapping parts_

| Part       | Example          | Meaning                                                                         |
| ---------- | ---------------- | ------------------------------------------------------------------------------- |
| Local port | `5432`           | The port fm listens on. It binds `127.0.0.1` only.                              |
| Host       | `db.example.com` | The destination. The proxy resolves the name in its region, never your machine. |
| Port       | `5432`           | The destination port. The connection leaves from your static IP.                |

## Credentials

fm reads credentials from a config file first, then from `FIXEDMARK_SOCKS_URL`. It never calls a Fixedmark API when it starts, so an API outage cannot stop your app from booting.

The default file is `$XDG_CONFIG_HOME/fixedmark/config.toml`, or `~/.config/fixedmark/config.toml`. Pass `--config` to name another one.

**Terminal**

```bash
# ~/.config/fixedmark/config.toml
app_id = "APP_ID"
token = "fmc_TOKEN"
endpoint = "mum.egress.fixedmark.com:1443"
```

Without a file, fm reads the SOCKS URL the dashboard shows. It accepts `socks5s://`, `socks5h://`, and `socks5://`, and always connects over TLS on port 1443.

**Terminal**

```bash
export FIXEDMARK_SOCKS_URL="socks5h://APP_ID:TOKEN@mum.egress.fixedmark.com:1080"
fm tunnel 5432:db.example.com:5432
```

The file holds your token. Keep it out of source control, mount it from your platform's secret store, and make it readable by your user only. fm never prints the token in logs or errors.

## Running beside your app

The tunnel must run as long as your app does. On a platform with one start command, start it in the background first:

**Terminal**

```bash
web: fm tunnel 5432:db.example.com:5432 & node server.js
```

- Your app may start before the tunnel listens. Retry the first connection, as most pools do.
- Serverless functions cannot keep a background process. Use a SOCKS5 driver there.
- If the endpoint is unreachable, fm retries with backoff, then exits with code 4.

## Logs and exit codes

fm writes one JSON line per event to standard output, or text with `--log text`. It logs `endpoint connected`, each `connection closed` with bytes and duration, and `endpoint unreachable, retrying`.

_fm exit codes_

| Code | Meaning                                                |
| ---- | ------------------------------------------------------ |
| `0`  | Stopped by SIGINT or SIGTERM.                          |
| `1`  | Failed for another reason, printed on standard error.  |
| `2`  | Bad usage, a bad config file, or no credentials found. |
| `3`  | The endpoint refused the credentials. Check the token. |
| `4`  | The endpoint stayed unreachable for the retry window.  |

A destination your [allowlist](https://fixedmark.com/docs/allowlists) refuses shows as `not_allowed` on that one connection. The tunnel keeps running.

## Other commands

These commands call the [REST API](https://fixedmark.com/docs/api) with an API key. Run `fm auth login` once to store the key, or set `FM_API_KEY`.

_fm commands for the REST API_

| Command                                                                 | What it does                                                                |
| ----------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| `fm auth login`                                                         | Stores an API key for the commands below.                                   |
| `fm whoami`                                                             | Shows the org and key in use.                                               |
| `fm apps list\|create\|get\|delete`                                     | Manages apps.                                                               |
| `fm ips list\|create\|get`                                              | Lists and orders static IPs.                                                |
| `fm credentials list\|create\|rotate\|revoke --app <app>`               | Manages proxy credentials. The token is printed once, on create and rotate. |
| `fm keys list\|create\|revoke`                                          | Manages API keys.                                                           |
| `fm routes create\|list\|show\|placement\|partners\|delete --app <app>` | Manages inbound routes, as Inbound routes shows.                            |
| `fm connectors create\|list\|rotate\|delete --app <app>`                | Manages connectors, as fm connectors shows.                                 |
| `fm jobs get\|watch <id>`                                               | Follows a provisioning job.                                                 |
| `fm usage --app <app>`                                                  | Shows bandwidth by day.                                                     |
| `fm api <METHOD> <path>`                                                | Calls any route in the API reference.                                       |
| `fm version`, `fm help`                                                 | Prints the version or the usage.                                            |

## Inbound routes

Inbound traffic uses `fm routes`, not fm tunnel. Routes belong to an app. `create` and `list` need `--app` with the app's id or name, and the other `fm routes` commands take it optionally. `fm routes create` gives a service on a connector's machine a hostname on the shared pool, or with `--dedicated-ip` a port on a dedicated IP of the app. See [Tunnels](https://fixedmark.com/docs/tunnels) and [Dedicated inbound IP](https://fixedmark.com/docs/inbound). Tunnels on Pro and up, with a dedicated inbound IP as an add-on at $9 per IP-month.

**Terminal**

```bash
# A hostname on the shared pool for a service on a connector's machine
fm routes create --app shop --kind http --name web --connector billing-db --to 127.0.0.1:8080 --partner 198.51.100.0/24

# A TCP port on a dedicated IP, to a host the node dials
fm routes create --app shop --kind tcp --name db --dedicated-ip IP_ID --origin db.example.com:5432 --partner 198.51.100.0/24

fm routes list --app shop
fm routes show web
fm routes placement web --dedicated-ip IP_ID
fm routes delete web
```

A route takes `--origin` or `--connector` with `--to`, never both. `--origin` needs `--dedicated-ip`, since a shared IP never dials out. `--proxy-protocol` goes with `--connector` only. `show`, `placement`, `partners set`, and `delete` name a route by id or name and search every app unless you pass `--app`. [fm routes reference](https://fixedmark.com/docs/tunnels#cli) lists every flag.

## fm connectors

fm connectors manages an app's connectors. Create and rotate write the new token to `fm-connector-NAME.token` in the current directory at mode 0600, print the install line, and never show the token again. `--token-file PATH` writes it somewhere else.

**Terminal**

```bash
fm connectors create --app APP_ID --name billing-db
fm connectors list --app APP_ID
fm connectors rotate --app APP_ID billing-db
fm connectors delete --app APP_ID billing-db
```

`list` shows each connector's token prefix, state, open sessions, and when it was last seen. `delete` is refused with `connector.in_use` while a route names the connector.

The connector itself is fixedmarkd, a separate program on the machine that holds your service, not an `fm` command. On that machine, from the directory with the token file, run the install line. [Connectors](https://fixedmark.com/docs/connectors) covers fixedmarkd's commands, service, status, and exit codes.

**Terminal**

```bash
sudo fixedmarkd install --token-file fm-connector-billing-db.token
```

## Frequently asked questions

### Does fm tunnel call a Fixedmark API when it starts?

No. It reads the config file or FIXEDMARK_SOCKS_URL and connects straight to your region's proxy, so your app starts even when the Fixedmark API is down.

### Is traffic through fm tunnel encrypted?

The hop from fm to the proxy is SOCKS5 inside TLS on port 1443, so your token never crosses the network in clear text. Your database's own TLS runs end to end inside the tunnel.

### Can fm tunnel accept inbound connections from the internet?

No. It listens on 127.0.0.1 and carries outbound traffic only. For inbound traffic, fm routes create gives a service a hostname through a connector, or a port on a dedicated inbound IP.
