Inbound
Updated
Connectors: fixedmarkd on your machine
A connector is fixedmarkd, the program that serves your tunnel routes from a machine with no public IP. It dials out on port 443 to each Fixedmark IP that carries one of its routes and holds a session open, and the node sends each partner connection down that session to a local target such as 127.0.0.1:8080. For an http route it also orders the hostname's certificate and terminates TLS on your machine. Your machine opens no inbound port.
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 to get yours first.
What a connector is
A connector is the origin of every tunnel route, and one of two kinds of origin on a dedicated inbound IP. A route's origin is either a host the node dials, which only a dedicated IP can do, or a connector and a target on the connector's machine. Use a connector when the service sits behind home NAT, office NAT, or carrier-grade NAT, or when you do not want to open a port to the internet.
- fixedmarkd dials each Fixedmark IP that carries one of its routes on TCP 443 with TLS 1.3, the server name
connector.fixedmark.net, and the ALPNfm-connector/1. It needs outbound 443 only. - It holds one session per IP, so two for a tunnel route on its two shared IPs, and opens a stream inside a session for each partner connection.
- The node checks the partner list before a stream opens, then meters the bytes as inbound and writes the connection log row, exactly as for a host origin.
- For a
tlsortcproute the bytes pass through unchanged. For anhttproute fixedmarkd terminates TLS on your machine with a certificate it orders itself, as Tunnels explains. No node ever terminates the partner's TLS.
Before you start
- A plan with tunnels. Tunnels on Pro and up, with a dedicated inbound IP as an add-on at $9 per IP-month. Your plan also sets how many connectors your org can hold, as Limits lists.
- An app for the connector and its routes.
fm apps listprints its id and name. - A Linux or macOS machine on amd64 or arm64 that can reach the service and make outbound connections to port 443. fixedmarkd does not run on Windows yet.
- The partner's source addresses, as IPv4 addresses or CIDRs.
Create a connector
Name the connector after the machine or the service it serves. A name is 1 to 64 letters, digits, dots, dashes, or underscores, starts with a letter or digit, and is unique within the app.
fm connectors create --app APP_ID --name billing-dbfm writes the connector's token to a file in the current directory, readable by you only, and prints the install line. The token is shown this once. Fixedmark stores only its SHA-256 hash and cannot show it again.
connector billing-db created id=CONNECTOR_ID
token_prefix xxxxxxxx
token written to fm-connector-billing-db.token (mode 0600)
install sudo fixedmarkd install --token-file fm-connector-billing-db.token
Run the install line from this directory. The token is never shown again.A token is fmc_ followed by 52 characters. fm connectors list and the dashboard show only the first 8 characters after fmc_, as its prefix. To write the file somewhere else, add --token-file PATH. fm never replaces a file that exists. The dashboard's connectors page for the app creates a connector too, and shows the same install line once.
Install fixedmarkd and the service
fixedmarkd is the connector program. It is separate from fm, needs no fm on the machine, and reads no fm configuration. The fixedmarkd download is not published yet.
On the machine that holds the service, from the directory with the token file, run the install line:
sudo fixedmarkd install --token-file fm-connector-billing-db.tokensudo fixedmarkd installcopies the token to a root-only file, writes the service, and starts it.- The service starts
fixedmarkd run, which attaches within seconds once a route names the connector. - systemd also gives the service a state directory for its certificate keys. On macOS install creates one at mode 0700, owned by
nobody.
After the install, delete the token file in your directory. The service holds its own copy.
Point a route at the connector
A route takes one origin. Pass --connector and --to for a target on the connector's machine. The target is host:port as that machine sees it, so 127.0.0.1, a private address, or a local name all work. Leave out --dedicated-ip and the route is a tunnel on the shared pool.
fm routes create --app APP_ID --kind http --name billing \
--connector billing-db --to 127.0.0.1:8080 \
--partner 198.51.100.0/24curl -X POST "https://api.fixedmark.com/v1/apps/APP_ID/routes" \
-H "Authorization: Bearer $FIXEDMARK_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"kind":"http","name":"billing","origin":{"kind":"connector","connector_id":"CONNECTOR_ID","target":"127.0.0.1:8080"},"partner_cidrs":["198.51.100.0/24"]}'Partners then reach https://billing.<org-slug>.<zone>, and each request reaches 127.0.0.1:8080 on your machine. tls and tcp routes take a connector the same way, with --kind tls or --kind tcp. fixedmarkd reads new routes from its plan within 60 seconds, so it needs no restart.
| Code | Meaning |
|---|---|
route.origin_conflict | The request names both a host origin and a connector. |
route.connector_not_found | No connector with that name or id exists in the route's app. |
route.connector_disabled | The connector is disabled. Contact support. |
route.target_invalid | The target is not host:port with a port from 1 to 65535. |
route.proxy_protocol_host_only | --proxy-protocol was set on a host origin. |
Run it in the foreground
To try a connector before you install the service, run it in a terminal. It reads the token from the file, which must be readable by you only.
chmod 600 fm-connector-billing-db.token
fixedmarkd run --token-file fm-connector-billing-db.token --log-format textWithout --token-file, fixedmarkd run reads the token from FIXEDMARKD_TOKEN, else from ~/.config/fixedmarkd/token, or ~/Library/Application Support/fixedmarkd/token on macOS. Stop it with Ctrl+C. It waits up to 10 seconds for open partner connections to finish, then exits 0.
| Flag | Default | Meaning |
|---|---|---|
--token-file | FIXEDMARKD_TOKEN | The token file. Refused when group or others can read it. |
--log-format | json | json or text. --log is the same flag. |
--api-url | https://api.fixedmark.com | The control plane. FIXEDMARKD_API_URL sets it too. |
--connect-timeout | 5s | How long to wait for the local target to accept. |
--max-streams | 256 | Partner connections open at once, from 1 to 2048. Past it a new one is refused with busy. |
--state-dir | $STATE_DIRECTORY, else $XDG_STATE_HOME/fixedmarkd, else ~/.local/state/fixedmarkd | Where the ACME account and each http route's key and certificate live, at mode 0700. See Certificates. |
--status-file | $XDG_RUNTIME_DIR/fixedmarkd/status.json | Where fixedmarkd status reads the state. Without XDG_RUNTIME_DIR, and for the service, /run/fixedmarkd/status.json. |
Run it as a service
sudo fixedmarkd install sets up the service for your OS. Run it again with a new --token-file after a rotate. Add --dry-run to print the files and commands without writing anything.
| OS | Service | Token | Logs |
|---|---|---|---|
| Linux | fixedmarkd.service, a systemd unit with its own dynamic user | /etc/fixedmarkd/token, mode 0600 | journalctl -u fixedmarkd |
| macOS | net.fixedmark.fixedmarkd, a launchd daemon that runs as nobody | /Library/Application Support/fixedmarkd/token, mode 0600 | /Library/Logs/fixedmarkd/fixedmarkd.log |
systemctl is-active fixedmarkd
journalctl -u fixedmarkd -fsudo launchctl print system/net.fixedmark.fixedmarkd
tail -f /Library/Logs/fixedmarkd/fixedmarkd.log- systemd restarts the service 5 seconds after any exit except exit codes 2, 3, and 4, which a restart cannot fix.
- launchd restarts every exit, so on macOS the service waits 5 minutes before it exits 3 or 4. A revoked token then asks the control plane once every 5 minutes.
sudo fixedmarkd uninstallstops the service and removes the filesfixedmarkd installwrote. It leaves any file it did not write, and it keeps the state directory, so the ACME account and the certificate keys stay on the machine until you remove the directory. On another OS, install exits 2 withfixedmarkd.install_unsupported.
Check the status
fixedmarkd status
sudo fixedmarkd status --token-file /etc/fixedmarkd/tokenfixedmarkd status prints one line per IP with its state, connecting, attached, detached, or stopped, the node, and the open streams. A CERTS table follows with one row per http route's hostname and its certificate's state. With --token-file it adds the control plane's view of each session. --json prints the status file, with the routes, the certificates, and each http route's counters.
fixedmarkd status prints no endpoints yet and the log says plan has no routes yet. Create a route with --connector and the session attaches within a minute.Fixedmark shows each placement's health beside the route. A placement reads healthy while a session to its IP is attached. When no session on that IP has been seen for 20 seconds, it reads no_session, the route's roll-up turns degraded or down, and the webhook route.health fires with origin_kind and connector_id. While no session is attached, a partner's connection opens and then closes, and your connection log shows result connector_offline.
The plan
fixedmarkd learns its routes from a plan it reads from the control plane every 60 seconds. After a dial fails it reads the plan again within about 8 seconds, then backs off to once a minute. The plan is version 2. It names each route's id, name, kind, hostname, target, and placements, the IPs to dial, and for http routes the CA and the challenge relay to use.
- A field fixedmarkd does not know is ignored, so a control plane that adds an optional field never stops the fleet.
- A newer version than fixedmarkd reads stops it with exit code 4,
fixedmarkd.plan_version. Upgrade fixedmarkd. - An older version stops it with exit code 5,
fixedmarkd.plan_version_old, and the service restarts until the control plane is upgraded. - A plan with a bad entry is refused whole, logged, and read again. The last good plan keeps serving.
Certificates
For each http route, fixedmarkd orders a certificate for the hostname by ACME DNS-01 and keeps the key in its state directory. Fixedmark only publishes the challenge record. No key leaves the machine. Tunnels covers the order, the renewal, and the weekly budget.
- Without a state directory fixedmarkd issues no certificate, and an
httproute refuses partners withcert_unavailable. - Run one fixedmarkd per state directory. Two on one directory would order the same certificates twice.
- Keep the state directory when you move the service. With an empty one, fixedmarkd makes a new key and orders each certificate again. Within 90 days of a hostname's last certificate, Fixedmark counts that order as a renewal, so it does not use the weekly budget. Let's Encrypt still allows only 5 certificates for the same names a week, so repeated moves can hit that limit. The route then refuses partners with
cert_unavailableuntil the limit clears.
Exit codes
| Code | Meaning |
|---|---|
0 | Stopped by SIGINT or SIGTERM, after up to 10 seconds for open streams to finish. |
1 | An unexpected error, such as fixedmarkd.api_not_found when the API URL serves no plan, or fixedmarkd.install_foreign_file when a service file exists that fixedmarkd did not write. Also fixedmarkd.state_dir when the state directory cannot be read or written. |
2 | Bad usage. Also fixedmarkd.token_file_mode (the token file is readable by group or others), fixedmarkd.token_missing, fixedmarkd.token_malformed, fixedmarkd.api_url_insecure, fixedmarkd.install_unsupported, fixedmarkd.install_needs_root, and fixedmarkd.state_dir (the state directory is a symbolic link or another user's). |
3 | The control plane refused the token with connector.token_invalid, so install a new token. Or Fixedmark disabled the connector with connector.disabled, which a new token does not change, so contact support. |
4 | The control plane sent a plan version this fixedmarkd does not read, fixedmarkd.plan_version. Upgrade fixedmarkd. The Linux service does not restart on it, and the macOS service waits 5 minutes first. |
5 | The control plane sent an older plan version than this fixedmarkd reads, fixedmarkd.plan_version_old. The control plane is behind, and the service restarts until it catches up. |
fixedmarkd run --help prints the same table.
Log lines
fixedmarkd writes one JSON line per event to standard output, or text with --log-format text. Every line carries service (fixedmarkd), version, and direction, every line after the first plan read adds connector_id, and no line carries the token.
| Line | When |
|---|---|
connector starting | At start. |
plan refreshed | Each plan read, every 60 seconds, with routes and endpoints. |
plan has no routes yet | The connector exists, but no route names it. |
connector attached | A session is up, with ip, node_id, and routes. |
connector detached | The session ended, with the reason and retry_in_ms. |
connector dial failed | The machine could not reach the IP on 443. |
stream opened, stream closed | One partner connection, with partner, target, bytes, and result. |
stream cap reached | Every stream slot is in use. New partners are refused with busy. |
connector token rejected | The control plane refused the token. Exit 3 follows. |
connector draining, connector stopped | Shutdown. |
PROXY protocol v2
Your service sees each partner connection arrive from fixedmarkd on its own machine. To give a tcp or tls target the partner's address, create the route with --proxy-protocol, or proxy_protocol: true in the API origin. fixedmarkd then sends a PROXY protocol v2 header before the first byte. The source is the partner's address and port, and the destination is the Fixedmark IP and the route's port. An http route takes no PROXY header: fixedmarkd sets X-Forwarded-For instead.
fm routes create --app APP_ID --kind tcp --name db \
--connector billing-db --to 127.0.0.1:5432 --proxy-protocol \
--partner 198.51.100.0/24proxy_protocol, and PostgreSQL poolers such as PgBouncer can. A service that does not expect it reads the header as data and fails the connection.Limits
| Plan | Connectors per org |
|---|---|
| Pro | 5 |
| Business | 20 |
| Scale | 50 |
These counts are proposed and may change before connectors leave early access. connector.quota answers a create past your plan's count, and connector.not_entitled (402) answers one on a plan without tunnels.
- fixedmarkd holds up to 256 partner connections open at once by default. Past that it refuses new ones with the reason
busy, and the partner's connection closes.--max-streamschanges the count. - The node gives one session 1000 streams. Past that a partner gets
connector_busy. - One session per connector and IP. A second fixedmarkd with the same token replaces the first, so run one per token. For more capacity, create more connectors.
- fixedmarkd pings the node every 15 seconds. The node closes a session that sends nothing for 45 seconds, and fixedmarkd reconnects.
What a connector cannot do:
- Carry UDP. Routes are TCP, and
tlsandhttproutes are TLS over TCP. - Open a port outside the route ranges. Partners reach ports 20000 to 29999 for
tcproutes, and 443 for every other kind, as inbound limits lists. - Bypass the partner list. The node admits only the partner addresses on the route, before anything reaches your machine.
- Reach a target that no route names. fixedmarkd opens only the targets in its plan and refuses any other with
target_not_in_plan.
Troubleshooting by close reason
Find the reason in the connector detached line, or the result in the connection log row, then follow its row.
| Reason | Meaning | What to do |
|---|---|---|
connector_replaced | A second session with the same token attached to the IP. The newest one wins. | Stop the other fixedmarkd, or create a second connector for it. |
token_rotated | The token was rotated. The old token now exits 3. | Run sudo fixedmarkd install --token-file with the new token file. |
connector_disabled | Fixedmark staff disabled the connector. | Contact support. |
connector_unbound | No route on the IP names the connector anymore, for example after a route moved to another group. | Nothing when the route moved: fixedmarkd reads its plan and dials the new IPs. Otherwise create a route with --connector, or uninstall the service. |
staff_disconnect | Fixedmark staff reset the session. | Nothing. fixedmarkd reconnects on its own. |
keepalive_timeout | The node heard nothing for 45 seconds. | Check the machine's network. If it repeats every few minutes, a NAT or firewall on its path drops idle flows. |
shutdown | The node restarted for an update. | Nothing. fixedmarkd reconnects within seconds. |
| Result | Meaning | What to do |
|---|---|---|
connector_offline | No session was attached on the IP. | Check fixedmarkd status and the service's logs for connector dial failed. |
target_unreachable | fixedmarkd could not open the target, with a reason such as connect_refused, timeout, dns, or busy. | Check the service listens on the route's --to address from the connector's machine. |
connector_busy | The session's 1000 streams were in use. | Set idle timeouts on the partner side, or spread partners over more connectors. |
When you write to support, include the connector_id and the reason from your log. Never include the token.
Rotate or delete a connector
fm connectors rotate --app APP_ID billing-db
sudo fixedmarkd install --token-file fm-connector-billing-db.token
fm connectors delete --app APP_ID billing-dbA rotate writes a new token file and closes the session at once, so install the new token right away. A delete is refused with connector.in_use while a route names the connector. Delete or repoint those routes first.
Frequently asked questions
Does a connector need an open inbound port on my machine?
No. fixedmarkd dials out to your Fixedmark IP on port 443 and holds the session open. Partner connections come back down that session, so the machine needs outbound 443 only.
Do partner allowlists still work with a connector?
Yes. Partners connect to the route's hostname or IP as with any inbound route, and the node admits only the partner addresses you list before anything reaches your machine.
Can I run fixedmarkd on Windows?
Not yet. fixedmarkd runs on Linux and macOS.
