How to print ZPL to a Zebra printer (every platform)
Zebra printers don't want a print dialog; they want raw ZPL bytes on TCP port 9100. Everything else (drivers, spoolers, "printing" from Notepad) is a detour that usually ends with your label code printed as text. Here's the direct route from every platform, plus the three traps that get everyone exactly once.
Linux / macOS: the nc one-liner
cat label.zpl | nc -w 0 192.168.1.50 9100
The printer's IP is on its network configuration sticker or self-test printout. Nothing comes back on success: port 9100 is fire-and-forget (more on that below).
Windows: PowerShell
$client = New-Object Net.Sockets.TcpClient('192.168.1.50', 9100)
$bytes = [IO.File]::ReadAllBytes('label.zpl')
$client.GetStream().Write($bytes, 0, $bytes.Length)
$client.Close()
For USB-connected printers on Windows: share the printer, then
copy /b label.zpl \\localhost\ZebraShareName. The /b (binary) flag is the
whole trick; without it Windows mangles the bytes.
From code
Any language that can open a TCP socket can print. Python:
import socket
with socket.create_connection(("192.168.1.50", 9100)) as printer:
printer.sendall(open("label.zpl", "rb").read())
The same five lines exist in PHP, C#, Java, Node: a socket and a write. No SDK required for delivery; the value of an API shows up in everything around delivery: rendering previews, verifying barcodes, and testing without hardware.
The three traps
1. No idle timeouts while printing. Zebras pause their TCP stack while the
head is physically printing. If you send a big batch with a client that gives up
on idle connections (nc -w 5, short socket timeouts), you'll get "broken pipe"
mid-transfer and a half-printed batch. Use -w 0 / no idle fuse for sends, and
give code-based senders generous write timeouts.
2. Don't loop 1,000 jobs at the printer. Each ^XA...^XZ job costs parsing
and buffer space; firing thousands in a tight loop can fill the receive buffer
and wedge the printer. For copies of the same label, one job with ^PQ1000 beats
a thousand jobs every time. For distinct labels, batch and pace them, and test
the burst against a virtual printer before aiming it at steel.
3. Silence is not success. Port 9100 sends no acknowledgment. The label you
"printed" may be sitting in a paused printer with its head up. Ask the printer
afterward: ~HS returns three status lines (jobs buffered, paper out, head
open, and more):
printf '~HS' | nc -w 3 192.168.1.50 9100 | cat -v
Paste the response into the host status decoder for a
field-by-field readout, or parse it programmatically with
POST /v1/host-status/parse.
No printer handy?
The free virtual Zebra printer listens on real TCP 9100, renders
whatever you send, answers ~HS like firmware, and lets you inject faults,
which makes it the safe place to test everything this guide just told you.
Published 2026-08-25 · Ben Faerber, software engineer, five years building warehouse fulfillment and label-printing systems; maintainer of pdf-to-zpl (26,000+ installs powering production label generation).