Port 9100 Printing: Raw TCP for POS, Explained

How raw port 9100 printing works, why every POS uses it, how to test it with netcat, and its blind spots — status, discovery, and error handling.

Published 2026-07-24 · Updated 2026-08-02 · For developers

Port 9100 printing — also called raw printing, JetDirect, or AppSocket — is the simplest protocol in printing: open a TCP connection to the printer's IP on port 9100, write bytes, close the socket. The printer prints exactly what you sent, with no job format, no handshake, and no acknowledgment. That radical simplicity is why virtually every POS system uses it, and also why its failure modes are so quiet.

What "raw 9100" actually is

There is no RFC for port 9100. HP shipped it in the early 1990s on JetDirect network cards, everyone copied it, and it became the de facto standard under three names that all mean the same thing:

  • JetDirect — HP's original name.
  • AppSocket — the name CUPS and much of Unix printing use (socket://192.168.1.87:9100).
  • RAW — what Windows calls it in the Standard TCP/IP port dialog.

Compare it with the two "real" network printing protocols and the trade is obvious:

Port 9100 (raw)LPR/LPD (port 515)IPP (port 631)
Job framingnone — a byte streamcontrol + data filesHTTP-based, structured
Status/feedbacknone built inqueue statusrich (job states, supplies)
Implementation efforta TCP socketmoderatehigh
On receipt printersuniversalrareeffectively absent

For a receipt printer, the bytes you write to that socket are ESC/POS commands: 1B 40 to initialize, text, 1D 56 00 to cut. The printer doesn't parse a "job" — it executes the stream as it arrives.

Why POS runs on 9100

  • Every network receipt printer supports it. Epson, Star, Bixolon, and every $60 generic all listen on 9100 out of the box. It's the one interface you can rely on across the entire fragmented market.
  • No drivers, no spooler. The POS opens a socket and writes. That's the entire integration — it works identically from Node, Python, Go, or a firmware blob.
  • Deterministic latency. The bytes go straight to the printer on the LAN. No cloud round trip, no polling interval — compare Star's CloudPRNT, where the printer polls a server and jobs wait seconds for the next poll.
  • It survives internet outages. The path is entirely local, which is precisely what you want for kitchen tickets.

Test it yourself

You don't need any POS software to check whether a printer speaks 9100.

With netcat (macOS/Linux, or ncat from Nmap on Windows):

# Is anything listening?
nc -zv 192.168.1.87 9100
 
# Print a test line: init, text, blank lines to clear the cutter, full cut
printf '\x1b\x40Hello from netcat\n\n\n\n\n\x1d\x56\x00' | nc 192.168.1.87 9100

With PowerShell (no tools needed on Windows):

# Is anything listening?
Test-NetConnection 192.168.1.87 -Port 9100
 
# Send a raw test print
$client = New-Object Net.Sockets.TcpClient "192.168.1.87", 9100
$stream = $client.GetStream()
$bytes  = [byte[]](0x1B, 0x40) +
          [Text.Encoding]::ASCII.GetBytes("Hello from PowerShell`n`n`n`n`n") +
          [byte[]](0x1D, 0x56, 0x00)
$stream.Write($bytes, 0, $bytes.Length)
$client.Close()

If paper feeds and cuts, you have a working raw printer. If the connection succeeds but nothing prints, you're usually sending plain text to a printer waiting in a page-description mode, or vice versa — receipt printers want ESC/POS, office lasers want PCL/PostScript.

No printer on the bench? The Proxy Nodes digital twin listens on :9100 and behaves like real hardware — same byte handling, same status replies — so both commands above work against localhost, free, before any hardware arrives.

The three blind spots of raw 9100 — and the fixes that ship

Raw printing's simplicity is subtraction: three things every other job protocol provides simply don't exist. Every POS "printer offline" support ticket traces back to one of these. None of them is unfixable — each has a remedy that ships today.

1. Fire-and-forget: a TCP ACK is not a printed receipt

When your write() returns, you know the printer's network stack buffered the bytes. You do not know they printed. If the paper ran out mid-job, the cover is open, or the cutter jammed, the socket looks exactly the same: bytes accepted, connection closed, ticket gone. The kitchen finds out when a customer asks where their food is.

This is the deepest flaw, and it's architectural — the protocol has no channel for "that job failed." Anything that looks like job confirmation has to be layered on top, and most POS integrations never bother.

The fix that ships: layer status onto the same socket with DLE EOT (next section), and query before and after every job. Every Proxy Nodes device answers those queries out of the box: firmware 1.0.24 reads paper, cover, and drawer state from the attached printer and replies to 10 04 n on :9100 in real time — and publishes the same state over GET /status and a live GET /events stream. When a state genuinely can't be read, the node reports it as unknown rather than guessing "fine."

2. No discovery

Port 9100 has no announcement mechanism. Nothing tells your POS a printer exists or what its address is — you type an IP by hand, usually one a vendor utility burned into the printer. The operational consequences:

  • A DHCP lease change silently kills printing; the fix is DHCP reservations or static IPs, and the discipline to document them.
  • Replacing a dead printer means configuring the newcomer to impersonate the old one's IP — the classic "swap failure" that takes a station down for an evening.

Office printers mitigate this with mDNS and SNMP; receipt printers mostly don't.

The fix that ships: announcement. A node advertises itself over mDNS as proxynodes-<id>.local (_proxynodes._tcp), so software finds it by a stable name instead of a hand-typed address that dies with a DHCP lease. Compatibility profiles (epson-tm-m30, star-tsp100, and others) re-advertise live, without a reboot. And nodes find each other: they beacon over UDP and serve GET /peers, so one known IP reaches every node on the LAN.

3. No back-pressure or queueing

A raw printer is a single socket with a small buffer. Most models service one connection at a time — a second connect during a job is refused or stalls until the first closes. There's no queue manager, no job priority, no "printer busy, retry in 2s" signal. Two terminals printing to one kitchen printer must serialize somewhere: in the POS backend, in a print server, or by luck. Under a Friday-night rush, "by luck" loses tickets.

The fix that ships: a request/response front end. On a Station Hub, software that can be changed prints through POST /print — every job gets an HTTP reply, so a busy or failing device produces an error you can retry instead of a socket that stalls in silence. Software that can't be changed keeps using raw :9100 unmodified; both paths drive the same printer.

DLE EOT: status over the same socket

Raw 9100 has no job protocol, but ESC/POS itself defines a tiny status conversation you can run over the same connection: send the 3-byte query 10 04 n and a compliant printer answers with a single status byte in real time — paper, cover, drawer, offline.

// Node: ask a raw printer for paper status (DLE EOT 4)
import net from "node:net";
 
const sock = net.createConnection(9100, "192.168.1.87");
sock.write(Buffer.from([0x10, 0x04, 0x04]));
sock.once("data", (buf) => {
  const b = buf[0];
  if ((b & 0x60) !== 0) console.log("paper out");
  else if ((b & 0x0c) !== 0) console.log("paper near end");
  else console.log("paper ok");
  sock.end();
});
setTimeout(() => console.log("no reply — status unknown"), 500);

Query before the job, query after, and you've recovered a useful fraction of what LPR/IPP would have given you — enough to stop sending tickets into a paper-out printer. It's a partial fix, though: many low-end printers answer nothing or answer wrong, and a silent timeout must be treated as unknown, not as "fine". The full byte layouts, the golden reply table, and the failure modes are in ESC/POS printer status: DLE EOT explained.

This two-way behavior is also the bar for anything presenting itself as a network printer: a device that accepts 9100 jobs but ignores 10 04 n gets marked offline by POS software that health-checks its printers. A Proxy Nodes device clears that bar: it accepts raw jobs on :9100 and answers DLE EOT from live sensor state, so a legacy POS drives it with zero integration. The same requirement applies when you put a USB-only printer on the network — see USB receipt printer to Ethernet.

Securing port 9100

Raw printing has no authentication, no encryption, and no authorization — anyone who can reach the port can print, cut, and fire the cash-drawer kick pulse. Internet-wide scans routinely find tens of thousands of printers exposed on 9100, and mass-print pranks (the 2018 "subscribe to PewDiePie" incident hit an estimated 50,000 of them) demonstrated how low the bar is. Sensible hygiene:

  • Never expose 9100 to the WAN. No port-forwards, ever. If remote printing is required, that's a VPN or a relay's job, not a firewall hole.
  • Put POS gear on its own VLAN. Printers, terminals, and payment devices on a segregated network, away from guest Wi-Fi. If you take cards, PCI DSS network-segmentation expectations push you here anyway.
  • Restrict by source. Where the switch/firewall allows it, permit 9100 only from POS terminal addresses.
  • Watch the drawer. The drawer kick is just another ESC/POS command (1B 70 ...); an open print port is an open-the-till port. Treat printer VLAN access as cash-handling access.

Where this leaves you

Raw 9100 is the right transport for POS printing — local, universal, fast. Just don't mistake it for a job protocol. Pair it with real status checks (DLE EOT, honestly interpreted), use hardware that announces itself or pin your addresses, serialize access to shared printers, and keep the port off the internet. For the bigger picture of how raw sockets compare with vendor SDKs, cloud relays, and local HTTP APIs, start with the receipt printer API guide.

Frequently asked questions

What is port 9100 printing?
A convention, not a standard: the printer listens on TCP 9100 and prints whatever bytes arrive, with no job format or handshake. It's also called RAW, JetDirect, or AppSocket. For receipt printers the bytes are ESC/POS commands.
How do I test if a printer supports port 9100?
Try to connect: nc -zv PRINTER_IP 9100 on macOS/Linux, or Test-NetConnection PRINTER_IP -Port 9100 in PowerShell. If it accepts, pipe a few bytes of ESC/POS at it and see if paper moves. No printer handy? The free Proxy Nodes digital twin listens on :9100 with the same byte handling.
Does port 9100 confirm that a job actually printed?
No. The TCP acknowledgment only means the printer buffered the bytes. Paper-out, cover-open, or a jam mid-job produce no error on the socket. The in-band remedy is querying DLE EOT status before and after the job — and treating no-reply as unknown, not as success.
Is port 9100 the same as LPR or IPP?
No. LPR (515) and IPP (631) are real job protocols with queues and status. 9100 is a bare byte stream. Receipt printers almost universally speak 9100 and rarely anything else, which is why POS software standardized on it.
Does a Proxy Nodes device answer DLE EOT on port 9100?
Yes. A node accepts raw ESC/POS jobs on :9100 and replies to 10 04 n status queries from live sensor state — paper, cover, drawer — so POS software that health-checks its printers sees a healthy, answering printer, with zero integration work.
Is it safe to expose port 9100 to the internet?
No. There is no authentication — anyone reaching the port can print, cut, and kick the cash drawer. Keep printers on a POS VLAN, never port-forward 9100, and use a VPN or relay if remote printing is genuinely needed.

Related reading