I’ll admit printing is the part of Linux system administration I deal with least often these days — most of my work lives entirely on screens. But every so often a compliance requirement or a physical signature form means someone needs a document printed from a headless Linux server or over SSH, and lpr is the command that quietly makes that possible without ever touching a GUI print dialog.
What lpr Does
lpr submits files to a print queue managed by CUPS (the Common Unix Printing System), which is the printing architecture used by virtually every modern Linux distribution. lpr itself is actually a BSD-heritage command-line interface preserved for compatibility — under the hood, on a modern Linux system, it’s talking to the same CUPS daemon (cupsd) that handles print jobs submitted through a desktop GUI.
lpr [OPTIONS] [FILE]...
Basic usage:
lpr document.pdf
This submits document.pdf to the default printer’s queue. If no file is given, lpr reads from standard input, which lets you pipe generated content straight to the printer:
ls -la /var/log | lpr
How lpr and CUPS Work Together
When you run lpr, it doesn’t talk to the printer hardware directly. Instead, it hands the file off to the CUPS scheduler (cupsd), which:
- Queues the job.
- Determines the appropriate filter chain to convert the input format (PDF, PostScript, plain text, image) into a format the target printer understands (often PostScript or a manufacturer-specific raster format via a PPD — PostScript Printer Description — file).
- Sends the converted data to the printer over the configured connection (USB, network/IPP, or a print server).
- Tracks job status, so tools like
lpqandlpstatcan report on it.
This architecture is why lpr supports printing so many different file types out of the box — the actual format conversion work is delegated to CUPS’s filtering system, not handled by lpr itself.
Core Options and Parameters
-P PRINTER — Select a Specific Printer
lpr -P office_laser document.txt
Without -P, lpr sends the job to the system’s configured default printer. You can list available printers with lpstat -p (a companion CUPS command) to find valid printer names for -P.
-#COPIES — Number of Copies
lpr -#3 report.pdf
Note the somewhat unusual syntax — the number of copies is attached directly after a # rather than being a separate argument, a holdover from lpr‘s traditional BSD option syntax.
-o OPTION=VALUE — Pass Print Job Options
This is where most of the practically useful control lives, since it passes options through to CUPS itself:
lpr -o sides=two-sided-long-edge document.pdf
lpr -o media=A4 document.pdf
lpr -o fit-to-page document.pdf
lpr -o number-up=2 document.pdf
sides=two-sided-long-edge/sides=two-sided-short-edge— duplex printing.media=SIZE— paper size (e.g.,A4,Letter,Legal).fit-to-page— scale content to fit the selected page size.number-up=N— print N logical pages per physical sheet (useful for draft printing to save paper).orientation-requested=4— landscape orientation (CUPS uses numeric IPP orientation codes;4is landscape,3is portrait).
Multiple -o flags can be combined in a single command:
lpr -P office_laser -o sides=two-sided-long-edge -o media=A4 -#2 report.pdf
-r — Remove the File After Spooling
lpr -r /tmp/generated_report.txt
Deletes the local file once it has been successfully handed off to the print spooler — useful for cleanup in automated report-generation-and-print scripts where the local copy is only a temporary artifact.
-h — Suppress the Burst Page
Disables the “banner” or “burst” page that some print environments insert between jobs to identify where one job ends and the next begins — useful in low-volume or single-user contexts where the banner page is just wasted paper.
-J NAME — Assign a Job Name
lpr -J "Q3 Financial Report" report.pdf
Assigns a human-readable name to the job as it appears in queue listings (lpq), which is helpful when multiple jobs are queued and you need to identify yours at a glance.
Companion Commands: Managing the Print Queue
lpr doesn’t work alone — a small family of BSD-heritage commands manage the print queue around it:
lpq — Check Queue Status
lpq -P office_laser
Lists pending jobs for the given printer, showing job IDs, owners, and status — the equivalent of checking “what’s still waiting to print.”
lprm — Remove a Job from the Queue
lprm -P office_laser 42
Cancels job ID 42 (as reported by lpq) before it prints — essential when you realize you sent the wrong file or the wrong number of copies.
lpstat — Check Printer and Job Status (CUPS-native)
lpstat -p # list printers and their status
lpstat -o # list pending jobs
lpstat -d # show the current default printer
lpoptions — View or Set Printer Options
lpoptions -p office_laser -l # list available options for a printer
lpoptions -d office_laser # set the system default printer
Practical, Real-World Examples
1. Printing a Report Generated by a Script
generate_monthly_report.sh > /tmp/report.txt
lpr -P accounting_printer -J "Monthly Report" -r /tmp/report.txt
2. Printing Double-Sided, Multiple Copies
lpr -P office_laser -o sides=two-sided-long-edge -#5 handout.pdf
3. Draft-Printing to Save Paper
lpr -o number-up=4 -o fit-to-page draft_document.pdf
Four logical pages per physical sheet is a common trick for quickly reviewing a long document without burning through paper for a draft that will be discarded.
4. Checking and Clearing a Stuck Queue
lpq -P office_laser
lprm -P office_laser - # remove all of the current user's jobs
5. Printing from a Remote Server Over SSH
ssh server 'cat /var/log/critical_report.txt' | lpr -P local_printer
This pulls content from a remote server and prints it on a locally connected printer, without needing to manually copy the file down first.
lpr in Shell Scripting and Automation
A pattern I’ve used for automated compliance reporting where a signed physical copy was still required by policy:
#!/bin/bash
REPORT="/tmp/compliance_report_$(date +%Y%m%d).pdf"
generate_compliance_report.py > "$REPORT"
if lpstat -p compliance_printer | grep -q "is idle"; then
lpr -P compliance_printer -o sides=two-sided-long-edge -J "Compliance $(date +%Y-%m-%d)" -r "$REPORT"
echo "Report sent to printer"
else
echo "Printer not ready, report saved at $REPORT" >&2
fi
Checking lpstat -p before submitting avoids silently queuing jobs to an offline or error-state printer without any feedback.
Comparing lpr to Related Commands
| Task | Best Tool |
|---|---|
| Submitting a print job (BSD-style) | lpr |
| Submitting a print job (System V-style, CUPS-native) | lp |
| Checking queue status | lpq / lpstat -o |
| Removing a queued job | lprm / cancel |
| Configuring printer options | lpoptions / cupsctl |
| Managing printers via web interface | CUPS web admin (http://localhost:631) |
Modern CUPS actually provides two parallel command-line interfaces for historical compatibility reasons: the BSD-style commands (lpr, lpq, lprm) and the System V-style commands (lp, lpstat, cancel). Both talk to the same underlying CUPS daemon and largely accomplish the same things with different syntax conventions — which one you use is mostly a matter of habit or what a particular script/tutorial happened to use. lp tends to have slightly more consistent, modern-feeling option syntax (lp -d printer -n 3 file.pdf versus lpr -P printer -#3 file.pdf), but both remain fully supported.
Troubleshooting Common lpr Issues
“lpr: Error – no default destination” — no default printer is configured; specify one explicitly with -P, or set a default with lpoptions -d printer_name.
Job stuck in the queue indefinitely — check lpstat -p for the printer’s status; a printer reporting an error state (out of paper, offline, jammed) will hold jobs indefinitely until the underlying issue is resolved.
Printed output looks garbled or is entirely blank pages — usually a driver/PPD mismatch; verify the correct driver is configured for the printer model via the CUPS web interface (http://localhost:631) or lpinfo -m to list available driver models.
Permission denied submitting a job — CUPS printer access can be restricted by user or group; check /etc/cups/printers.conf and the printer’s configured access policy, or consult with whoever administers the print server.
Performance Optimization
For high-volume printing, batching multiple files into a single lpr invocation (lpr file1.pdf file2.pdf file3.pdf) reduces per-job scheduling overhead compared to invoking lpr separately for each file. For very large documents, ensure the CUPS spool directory (/var/spool/cups) has adequate free disk space, since large jobs are spooled to disk before being sent to the printer.
Security Implications
Print jobs pass through the CUPS spool directory and, for network printers, travel over the network (often via IPP) — sensitive documents printed to a shared or network printer can potentially be intercepted or left sitting in an output tray for anyone to see. For sensitive material, use printers with an authentication/release feature (holding jobs until the user authenticates at the printer) if your environment supports it, and ensure CUPS’s web administration interface isn’t exposed beyond localhost or the intended management network, since misconfigured CUPS instances have historically been a source of information disclosure and, in some cases, remote code execution vulnerabilities.
Compatibility Across Distributions
lpr is provided by the cups-bsd package (part of the broader CUPS printing system) and is available on virtually all major Linux distributions — Ubuntu, Debian, Fedora, RHEL/CentOS, Arch, openSUSE — though it may need to be explicitly installed on minimal server images, since a full printing stack isn’t always included by default on headless server installations. macOS also uses CUPS under the hood and includes lpr by default. The core options (-P, -#, -o) behave consistently across these because they’re standardized by CUPS itself rather than varying per distribution.
Understanding the CUPS Architecture Behind lpr
To really understand what lpr is doing, it helps to walk through the CUPS architecture layer by layer. At the center sits cupsd, a background daemon that listens (by default on port 631) for print requests using the Internet Printing Protocol (IPP), an HTTP-based protocol. When you run lpr, it’s essentially constructing and sending an IPP “create job” and “send document” request to cupsd, whether that daemon happens to be running locally or on a remote print server.
Once cupsd receives a job, it consults its configuration to determine which “class” of filter chain the destination printer needs. CUPS ships with a set of filters capable of converting many common formats — plain text, PostScript, PDF, and various image formats — into a format the printer itself understands, which for many printers means either PostScript or a raster format described by that printer’s PPD (PostScript Printer Description) file. Modern “driverless” printers that support IPP Everywhere or AirPrint-style protocols can often skip much of this legacy filter chain entirely, since they accept a more directly renderable format from CUPS without needing a printer-specific driver at all.
Backend modules then handle the final step of actually transmitting the processed job to the physical printer, whether that’s over USB, a network socket, or another print server acting as an intermediary. This layered design — client tool (lpr) → daemon (cupsd) → filter chain → backend → hardware — is why lpr itself can remain such a small, simple command: nearly all the real complexity of format conversion and device communication is handled elsewhere in the CUPS stack, not in lpr itself.
Configuring and Discovering Printers
Before lpr can be useful, at least one printer needs to be configured in CUPS. This is commonly done through the CUPS web administration interface (http://localhost:631/admin), though it can also be done entirely from the command line using lpadmin:
lpadmin -p office_laser -E -v socket://192.168.1.50 -m everywhere
This example adds a network printer named office_laser, reachable via raw socket printing at a given IP address, using CUPS’s generic “everywhere” driver profile for modern driverless-capable printers. Once added, lpstat -p will list it, and lpr -P office_laser becomes usable immediately.
Handling Common Print Job Formats
lpr and CUPS handle format detection largely automatically, using logic similar in spirit to the file command discussed elsewhere in this series — inspecting file content rather than trusting the extension. This means you can generally hand lpr a PDF, a plain text file, a PostScript file, or a common image format without needing to specify the format explicitly:
lpr report.pdf
lpr notes.txt
lpr diagram.png
If automatic detection ever misidentifies a file’s format, the -o document-format option can be used to force a specific MIME type interpretation, though this is rarely necessary in practice with modern CUPS installations.
Monitoring and Auditing Print Activity
For environments where print activity needs to be tracked (cost accounting, compliance, or simple troubleshooting of “who printed what”), CUPS maintains logs that can be inspected directly, complementing what lpq/lpstat show for currently queued jobs:
sudo tail -f /var/log/cups/page_log
This log records completed print jobs with details including the user, printer, and page count, which is useful both for auditing purposes and for diagnosing whether a job that seemed to vanish from the queue actually completed successfully or failed silently.
Summary
lpr is the command-line front door to CUPS-managed printing on Linux — deceptively simple on the surface (lpr file.pdf) but backed by the full flexibility of CUPS’s option system via -o for duplexing, paper size, scaling, and page layout. Paired with lpq for queue status and lprm for cancellation, it covers everything needed to manage print jobs entirely from the command line, which matters more than it might seem on headless servers or in automated reporting pipelines where a GUI simply isn’t available.
References
- CUPS Documentation: https://www.cups.org/doc/
- CUPS Command-Line Printing Guide: https://www.cups.org/doc/options.html
man lpr/man lpq/man lprm(local manual pages)
