Linux Printing & CUPS
CUPS print subsystem: software validation, daemon lifecycle, printer discovery, queue management, job control, and security posture.
Software Validation
# STEP 1: Is CUPS installed?
command -v lpstat && echo "CUPS client tools present" || echo "CUPS not installed"
pacman -Qi cups 2>/dev/null | awk '/^Name|^Version|^Install Date/'
# STEP 2: Is the scheduler running?
lpstat -r # "scheduler is running" or "not running"
systemctl is-active cups # active / inactive
systemctl is-enabled cups # enabled / disabled
# STEP 3: Any printers configured?
lpstat -p -d # Printers + default destination
lpstat -t # Full status: scheduler, devices, jobs
# WHAT YOU'LL SEE WHEN NOTHING IS CONFIGURED:
# lpstat: No destinations added.
# no system default destination
Installation
# ARCH LINUX (pacman)
# cups pulls: qpdf, libcupsfilters, libppd, cups-filters
sudo pacman -S cups
# RHEL/CENTOS/ROCKY (dnf)
sudo dnf install cups
# DEBIAN/UBUNTU (apt)
sudo apt install cups
# VERIFY INSTALL — 5 packages on Arch:
# qpdf — PDF transformation library
# libcupsfilters — Filter library for CUPS backends
# libppd — PPD file support library
# cups-filters — Print filters (PDF → raster conversion)
# cups — The daemon itself
# POST-INSTALL: pacman creates:
# - Group 'cups' (GID 209)
# - User 'cups' (UID 209) — the daemon's service account
# - systemd unit: cups.service
Daemon Lifecycle
# START + ENABLE (persist across reboot)
sudo systemctl enable --now cups (1)
# VERIFY
systemctl status cups # Active, enabled, no errors
lpstat -r # "scheduler is running"
# COMMON MISTAKE: start without enable
sudo systemctl start cups # Runs NOW but gone after reboot
sudo systemctl start --now cups # Same thing — --now is redundant with start
# WITHOUT SUDO:
# "Failed to start cups.service: Access denied"
# CUPS is a system service — requires privilege escalation
# RESTART AFTER CONFIG CHANGES
sudo systemctl restart cups
# STOP (troubleshooting only)
sudo systemctl stop cups
Printer Discovery
# WHAT BACKENDS ARE AVAILABLE?
lpinfo -v # Lists all detected devices + URIs
# Output examples:
# network lpd
# direct usb://Brother/HL-L2350DW?serial=...
# network socket
# network ipp
# network ipps
# network http
# network https
# direct hpfax
# WHAT DRIVERS ARE AVAILABLE?
lpinfo -m | wc -l # How many total
lpinfo -m | grep -i brother # Filter by brand
lpinfo -m | grep -i hp
lpinfo -m | grep -i epson
# USB PRINTERS — verify kernel sees it
lsusb | grep -i print
# Or broader:
lsusb | grep -iE 'brother|hp|epson|canon|lexmark'
# NETWORK PRINTERS — probe common ports
# IPP: 631, JetDirect/raw: 9100, LPD: 515
ss -tlnp | grep -E '631|9100|515'
# AVAHI/MDNS DISCOVERY (if cups-browsed installed)
avahi-browse -rt _ipp._tcp # Find IPP printers on network
avahi-browse -rt _printer._tcp # Find LPD printers on network
Printer Management
# ADD A PRINTER
# Syntax: lpadmin -p <name> -v <uri> -m <driver> -E
# -p printer name (no spaces — use dashes)
# -v device URI (from lpinfo -v)
# -m driver/model (from lpinfo -m)
# -E enable + accept jobs immediately
# USB example:
sudo lpadmin -p brother-hl2350 \
-v "usb://Brother/HL-L2350DW?serial=U12345" \
-m "everywhere" \
-E
# Network example (IPP):
sudo lpadmin -p office-hp-4015 \
-v "ipp://10.50.1.100/ipp/print" \
-m "everywhere" \
-E
# Network example (raw/JetDirect socket):
sudo lpadmin -p lab-printer \
-v "socket://10.50.1.101:9100" \
-m "everywhere" \
-E
# "everywhere" = driverless IPP Everywhere (modern printers)
# For older printers, use specific PPD from lpinfo -m
# SET DEFAULT PRINTER
lpoptions -d brother-hl2350 # User default
sudo lpadmin -d brother-hl2350 # System default
# VERIFY
lpstat -p -d
# printer brother-hl2350 is idle.
# system default destination: brother-hl2350
# REMOVE A PRINTER
sudo lpadmin -x old-printer-name
# DISABLE/ENABLE (pause queue without removing)
cupsdisable brother-hl2350 # Stop accepting jobs
cupsenable brother-hl2350 # Resume
Print Jobs
# BASIC PRINT (to default printer)
lp document.pdf
lp -n 2 document.pdf # 2 copies
lp -t "Monthly Report" document.pdf # Job title
# PRINT TO SPECIFIC PRINTER
lp -d brother-hl2350 document.pdf
# PRINT OPTIONS
lp -o sides=two-sided-long-edge doc.pdf # Duplex (long edge)
lp -o sides=two-sided-short-edge doc.pdf # Duplex (short edge)
lp -o number-up=2 doc.pdf # 2 pages per sheet
lp -o number-up=4 doc.pdf # 4 pages per sheet
lp -o page-ranges=1-5 doc.pdf # Specific pages
lp -o media=A4 doc.pdf # Paper size
lp -o landscape doc.pdf # Landscape orientation
lp -o fit-to-page doc.pdf # Scale to fit
# COMBINE OPTIONS
lp -o sides=two-sided-long-edge \
-o number-up=2 \
-o page-ranges=1-10 \
doc.pdf
# PIPE FROM STDIN
awk 'NR>=10 && NR<=50' /var/log/syslog | lp # Print log excerpt
man -t cups | lp # Print formatted manpage
# JOB MONITORING
lpstat -W active # Active jobs
lpstat -W completed # Completed jobs
lpstat -o # All jobs on all printers
lpstat -o brother-hl2350 # Jobs on specific printer
# JOB CONTROL
cancel <job-id> # Cancel specific job
cancel -a # Cancel ALL your jobs
cancel -a brother-hl2350 # Cancel all on one printer
lp -i <job-id> -H hold # Hold a job
lp -i <job-id> -H resume # Resume a held job
CUPS Web Interface
# CUPS runs an HTTP admin interface on port 631
# Access: http://localhost:631
# VERIFY IT'S LISTENING
ss -tlnp | grep 631
# LISTEN 0 5 127.0.0.1:631 * users:(("cupsd",pid=XXXX,fd=7))
# HTTPS NOTE (from post-install warning):
# First HTTPS access triggers SSL certificate generation
# This can take 30-60 seconds — not a hang, just key generation
# ALLOW REMOTE ADMIN (multi-user server)
# Edit /etc/cups/cupsd.conf:
# Listen 0.0.0.0:631 (instead of Listen localhost:631)
# <Location /admin>
# Allow @LOCAL (allow LAN hosts)
# </Location>
# Then: sudo systemctl restart cups
# SECURITY: On a server, verify binding scope
ss -tlnp | grep 631
# If 0.0.0.0:631 — anyone can reach the admin UI
# If 127.0.0.1:631 — localhost only (default, secure)
Security Posture
# CUPS ATTACK SURFACE — what to audit on any Linux host
# 1. Is CUPS exposed beyond localhost?
ss -tlnp | grep 631
# 127.0.0.1:631 = safe (default)
# 0.0.0.0:631 = exposed — intentional?
# 2. Who can administer?
grep -E '^<Location|Allow|Deny|Require' /etc/cups/cupsd.conf
# 3. Print spool contents (sensitive data at rest)
ls -la /var/spool/cups/
# Jobs sit here as files — readable by cups group
# On shared systems: completed jobs may linger
# 4. IPP is HTTP underneath
# ipp:// = plaintext — interceptable on the wire
# ipps:// = IPP over TLS — encrypted
# Enterprise environments MUST use ipps://
# 5. Log files — what CUPS records
ls -la /var/log/cups/
# access_log — who printed what, when
# error_log — daemon errors, auth failures
# page_log — page counts per job (audit trail)
# 6. Service account
id cups
# uid=209(cups) gid=209(cups) groups=209(cups)
# Dedicated service account — principle of least privilege
# 7. Firewall — should 631 be open?
# Only if this machine serves as a print server
# Client machines: 631 inbound should be BLOCKED
sudo iptables -L -n | grep 631
# Or nftables:
sudo nft list ruleset | grep 631
Troubleshooting
# SCHEDULER NOT RUNNING
lpstat -r # Confirm
sudo systemctl status cups # Check for errors
journalctl -u cups --no-pager -n 50 # Recent logs
# NO DESTINATIONS
lpstat -p -d # Should list printers
lpinfo -v # Can CUPS see any devices?
lsusb # USB connected?
# JOB STUCK IN QUEUE
lpstat -o # Show all jobs
cancel -a # Nuclear: clear all
sudo systemctl restart cups # Reset the scheduler
# PERMISSION DENIED
# User must be in 'lp' or 'cups' group for some operations
groups # Check your groups
sudo usermod -aG lp $USER # Add yourself
# Log out/in for group change to take effect
# DRIVER ISSUES
lpinfo -m | grep -ic everywhere # Driverless support?
# If your printer isn't "everywhere" compatible:
# Install manufacturer drivers (AUR for Arch, vendor repos for RHEL)
# Brother: brother-hll2350dw (AUR)
# HP: hplip (official repos)
# Epson: epson-inkjet-printer-escpr (official repos)
# CUPS ERROR LOG — the definitive source
sudo tail -50 /var/log/cups/error_log
# LogLevel in /etc/cups/cupsd.conf: warn → info → debug → debug2
Enterprise Patterns
# FLEET DEPLOYMENT — scripted printer setup
# Idempotent: safe to run multiple times
PRINTER_NAME="dept-hp-4015"
PRINTER_URI="ipp://print.corp.example.com/printers/hp4015"
if ! lpstat -p "$PRINTER_NAME" 2>/dev/null; then
sudo lpadmin -p "$PRINTER_NAME" \
-v "$PRINTER_URI" \
-m everywhere \
-E
echo "Printer $PRINTER_NAME added"
else
echo "Printer $PRINTER_NAME already configured"
fi
# SET AS DEFAULT IF NO DEFAULT EXISTS
if ! lpstat -d 2>/dev/null | grep -q 'system default'; then
sudo lpadmin -d "$PRINTER_NAME"
fi
# AUDIT: What printers exist across a fleet
# (run via Ansible, pdsh, or similar)
lpstat -p -d 2>/dev/null || echo "CUPS not configured"
# PRINT SERVER: Accept jobs from network clients
# /etc/cups/cupsd.conf:
# Browsing On
# Listen *:631
# <Location />
# Allow @LOCAL
# </Location>
# Clients discover via Avahi/mDNS or manual URI
Quick Reference
# VALIDATION
command -v lpstat # CUPS installed?
lpstat -r # Scheduler running?
lpstat -p -d # Printers + default
# LIFECYCLE
sudo systemctl enable --now cups # Start + persist
sudo systemctl restart cups # After config changes
# DISCOVERY
lpinfo -v # Available devices/URIs
lpinfo -m | grep -i <brand> # Available drivers
# ADD PRINTER
sudo lpadmin -p <name> -v <uri> -m everywhere -E
lpoptions -d <name> # Set default
# PRINT
lp file.pdf # Default printer
lp -d <name> file.pdf # Specific printer
lp -o sides=two-sided-long-edge file.pdf # Duplex
# JOBS
lpstat -W active # Active jobs
cancel <job-id> # Cancel job
cancel -a # Cancel all
# SECURITY AUDIT
ss -tlnp | grep 631 # Binding scope
ls -la /var/spool/cups/ # Spool contents
sudo tail -20 /var/log/cups/error_log # Error log