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