Skip to content

NATS for firewall admins

You do not need to know NATS to run pfCentral. This page is for the moment you see a word like subject, callout or JetStream in a log and want to know what it means. It assumes you know firewalls and TCP, and nothing about message brokers.

NATS is a message broker. Think of it as a post office that programs connect to:

  • A program publishes a message to an address. NATS calls the address a subject.
  • A program subscribes to a subject to receive every message sent to it.
  • A message can ask for an answer. That is request and reply: the sender waits a few seconds for one reply.

The sender does not need the receiver’s IP address, port or hostname. It only needs the subject. NATS does the delivery.

In pfCentral the post office runs inside the server. You do not install, patch or configure a separate NATS server unless you choose to bring your own.

A post office only works if everyone visits it. In NATS, every program opens one outbound TCP connection to the broker and keeps it open. Messages flow both ways on that connection.

So the agent on your firewall connects out to the server’s NATS port (24222), exactly as a browser connects out to a website. The server never opens a connection to the firewall. When the server has a job for the firewall, it publishes a message, and NATS sends it down the connection the agent already opened.

Firewall agents open outbound connections to the NATS broker inside the pfCentral server. Requests from the web UI travel back down those connections. remote sites, behind NAT your network or cloud fw-hq-01 Brisbane HQ pfsense.fw-hq-01.> fw-syd-02 Sydney branch pfsense.fw-syd-02.> fw-mel-03 Melbourne DC pfsense.fw-mel-03.> pfCentral server NATS broker port 24222 Web UI + API port 2808 PostgreSQL devices, users You ✓ pfsense.fw-hq-01.firewall.rules.create applied ✓
  1. 1. Nothing listens on your firewalls

    Your firewalls sit at remote sites, often behind NAT. They accept no incoming management connections, and pfCentral never asks you to open one.

  2. 2. Each agent dials out

    The agent on each firewall opens one outbound connection to the server's NATS port, the same way a browser opens a website. If the firewall can reach the server, it works.

  3. 3. The connection stays open

    Every few seconds the agent sends a heartbeat and live stats (CPU, memory, traffic) up the connection. This is how the UI knows a firewall is online.

  4. 4. You click Save

    The server publishes a request to an address called a subject. Only fw-hq-01 listens on pfsense.fw-hq-01.…, so only it receives the request. It travels down the connection the agent already opened.

  5. 5. The agent applies it and replies

    The agent writes the change to the pfSense configuration, applies it, and sends the result back the same way. The UI shows you the outcome.

  6. 6. Each firewall only hears its own subjects

    When an agent connects, the server gives it a pass that allows pfsense.<its-id>.> only. One firewall can never read or answer messages for another.

fig. — how a change reaches a firewall

From the firewall’s point of view, this is an outbound session to one host and one port, like any other. It works through NAT, CGNAT, and upstream routers you do not control.

A subject is a string made of words separated by dots. pfCentral uses one pattern everywhere:

pfsense.<device-id>.<resource>.<action>

Try it. Pick a firewall and an action, and see which agent receives the message:

Which firewall?
What?
Do what?
pfsenseproduct . fw-hq-01which firewall . firewall.ruleswhat . listdo what
fw-hq-01 may use pfsense.fw-hq-01.>
fw-syd-02 may use pfsense.fw-syd-02.>
fw-mel-03 may use pfsense.fw-mel-03.>
Pick a firewall and an action. The message goes only to the agent whose pass matches the subject.

The device ID in the middle is what keeps firewalls apart. The agent on fw-syd-02 subscribes only to subjects that start with pfsense.fw-syd-02.. A message for fw-hq-01 never reaches it.

In the real system the device ID is the firewall’s SMBIOS UUID, such as 4c4c4544-0042-5a10-8048-b4c04f4d3132, not its name. Names can change; the UUID does not.

NATS permissions and subscriptions can use two wildcards:

WildcardMatchesExample
*exactly one wordpfsense.*.telemetry.metrics matches telemetry from every firewall
>one or more words, at the endpfsense.fw-hq-01.> matches everything for fw-hq-01

You will see pfsense.<id>.> in the pfSense package’s Status tab as the agent’s credential scope. It means “this agent may only send and receive messages about itself”.

Anyone who can reach port 24222 can try to connect, so NATS must check who is connecting and what they may do. pfCentral uses three ideas for this.

Accounts are separate rooms inside one broker. The server’s own connection, the devices, and the login checker live in different accounts. Messages do not cross between accounts unless the server allows it.

The sentinel is a shared login that every agent uses to knock on the door. On its own it has no permissions at all. It only gets the agent into the waiting room.

The authorization callout is how the door is opened. When an agent knocks, NATS asks pfCentral: “this is device X with secret Y, may it in?” pfCentral checks the secret against its database and that the device is enrolled. If yes, it signs a short-lived pass (a JWT) that allows pfsense.<device-id>.> and nothing else. The pass expires after one hour, and the agent gets a new one when it reconnects.

The enrollment page animates this sequence step by step.

Plain NATS is fire and forget: if nobody listens, the message is gone. JetStream is the part of NATS that stores messages. pfCentral uses it for three things:

NameKindHoldsKept for
TELEMETRYstreamCPU, memory, interface and gateway samples from every agent6 hours
device-presencekey-value bucketone entry per online device15 seconds after the last heartbeat
device-statekey-value bucketrecent answers from devices, such as rule lists, so pages load fast5 minutes

The presence bucket is how pfCentral knows a firewall is online. Read Online, offline and telemetry for the full story, with a simulator.

JetStream stores its data in the server’s state directory, under nats/js/. Losing it only loses recent telemetry and caches.

When you open a firewall’s rule list, the server sends a request like this:

subject: pfsense.4c4c4544-0042-5a10-8048-b4c04f4d3132.firewall.rules.list
reply-to: _INBOX.Yk3Zr7c… (a one-time return address)
payload: {"request_id":"cs1q…","payload":{}}

The agent answers on the return address:

{ "request_id": "cs1q…", "device_id": "4c4c4544-…", "data": [ …rules… ] }

The server waits up to 5 seconds. If the firewall is offline or busy, you see a timeout error in the UI instead of a hang.

TermMeaning
Broker, serverThe NATS process. In pfCentral it runs inside the pfCentral server.
SubjectThe address of a message, such as pfsense.<id>.firewall.rules.list.
Publish, subscribeSend to a subject; listen on a subject.
Request, replyA message that expects one answer on a temporary _INBOX. subject.
Micro serviceA NATS convention for request and reply handlers. The agent is one, named pfsense-agent.
AccountAn isolated space inside one broker.
OperatorThe top-level key that signs accounts. pfCentral creates it on first boot.
JWT, credsA signed pass that says who you are and what you may do. A .creds file holds one plus its key.
SentinelThe shared, permission-less login agents use to reach the callout.
Authorization calloutNATS asking pfCentral whether to admit a connection.
JetStreamNATS storage: streams and key-value buckets.
$SYSNATS’s own system account. It reports connects and disconnects.