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 · 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 ProxyNodes digital twin listens on :9100 and behaves like real hardware — same byte handling, same status replies — so both commands above work against localhost.

The three blind spots of raw 9100

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.

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 (see DLE EOT below), and most POS integrations never bother.

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. (This is why a LAN print device that announces itself over mDNS — proxynode-<id>.local — removes a whole category of setup errors.)

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.

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 cheap 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 emulating a printer: a device that accepts 9100 jobs but ignores 10 04 n will be marked offline by POS software that health-checks its printers. A ProxyNode — the prototype-stage device we're building — emulates the full conversation: it accepts raw jobs on :9100 and answers DLE EOT from live sensor state, so legacy POS systems drive it with zero integration. The same applies to putting 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 cheap the attack 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), pin your addresses or use hardware that announces itself, 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.
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 only in-band remedy is querying DLE EOT status before and after the job — and treating no-reply as unknown.
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.
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