AirPrint with CUPS

By Connor Taffe | Published .

Using Linux and other free software, it's possible to revive an older but perfectly functioning printer as an AirPrint printer, allowing simple driverless printing from your iPhone and other devices. This article outlines a solution using the following:

In this article we connect the print server to the printer over the network, which requires a printer with a network interface possibly via a network card. A print server is any physical or virtual machine running Linux with CUPS and Avahi installed, in my case it's a VM running on VMWare on an HP ProLiant DL380G7 rack server. The printer can also be physically connected to the print server through USB, serial, parallel, or any other mechanism supported by CUPS. It could even be an authenticating IPP print service being accessed over the Internet.

Printing from your iPhone utilizes AirPrint, an Apple technology built on several standards: Internet Printing Protocol (IPP) and mDNS. IPP, controlled by the Printer Working Group provides a standard way to send jobs to a printer, track jobs, receive errors, etc; Multicast DNS (mDNS) or DNS Service Discover (DNS-SD), also called zero configuration networking or ZeroConf1, is a way to advertise services on the local network alongside capabilities information. IPP Everywhere is an open standard which works similarly to AirPrint, and is compatible with it.

Installing Linux on your chosen target system is out of scope for this article, in my case I uploaded an ISO to VMWare and booted a new VM with it as the CD-ROM, once installed I did some housekeeping:

# Connecting over SSH as my user
; mkdir -p .ssh
; chmod 700 .ssh/
; cd .ssh/
# Add my SSH public key so I can login without a password
; echo 'my-public-key' >> authorized_keys
; chmod 600 authorized_keys
# Allow VMWare better interop with the guest
; sudo dnf install open-vm-tools

Goal

Our goal print job flow is the following:

%3iphoneiPhonevmPrint Server VMAvahi + CUPSiphone->vmmDNS/IPPhp4100nHP LaerJet 4100N(EIO network card)vm->hp4100nAppSockethp650cHP DesignJet 650C(MIO network card)vm->hp650cAppSocket

In more detail:

AppSocket is a protocol developed by Tektronix in which a driver can talk to a printer over a TCP connection to port 9100 on the printer's network interface -- the protocol is also called JetDirect (because of its usage with JetDirect cards) or RAW. The HP printer listens for commands from network connections as if it were directly connected to a PC over serial or parallel, supporting the same formats such as Adobe PostScript, HP Printer Command Language (PCL), or its subset HP Raster Transfer Language (RTL).

It is imperitive that the printer's network interface not be accessible from the Internet. Access to the printer network interface should be limited to the local network, and preferably only accessible by the print server via VLAN or physical network partition. Since it has no authentication mechanism, AppSocket will accept anything sent to it and print it. You can navigate to your printer's IP on port 9100, e.g. http://printer.home.arpa:9100, and the printer will print out the incoming HTTP request as if it were a print job.

Installing and Configuring

To install CUPS and Avahi, you should be able to use your distro's package manager:

; sudo dnf install cups

# These may be unnecessary
; sudo systemctl enable cups --now
; sudo systemctl enable avahi --now

The cups package installs Avahi through its dependency on cups-browsed on Fedora, but may not on all systems.

If you use a firewall, you need to poke some holes to allow your devices to communicate with CUPS over your local network:

; sudo firewall-cmd --zone=public --add-service ipp --permanent
; sudo firewall-cmd --zone=public --add-service mdns --permanent
; sudo firewall-cmd --reload

Now IPP is one of the allowed services:

; sudo firewall-cmd --list-services
... ipp mdns ...

If you're using an HP printer, you'll want to install hplip so that CUPS can use those drivers:

; sudo dnf install hplip

Configuring CUPS

To configure CUPS to accept traffic from other network devices, we need to edit /etc/cups/cupsd.conf, by replacing the Listen directive for localhost with a general Port directive:

#Listen localhost:631
Port 631

To allow all (even unauthenticated admin) access from your local network to the Web UI, add this to /etc/cups/cupsd.conf (more information at Red Hat and man cupsd.conf):

LogLevel info
MaxLogSize 1m
ErrorPolicy stop-printer
ServerAlias *
# Allow remote access
Port 631
Listen /run/cups/cups.sock
WebInterface Yes
IdleExitTimeout 60
<Location />
  Allow @LOCAL
</Location>

A LogLevel of info will give you more information on when the Web UI or IPP service is being used. The Location block is required to allow printing from the local network. Omitting any Policy blocks seems to allow any user to access the admin Web UI without authentication. Disabling Browsing means CUPS won't produce mDNS entries of its own, instead we'll do that manually so they work with AirPrint. You'll need to restart CUPS to see config changes reflected:

; sudo systemctl restart cups.service

At this point you'll want to configure your printer in CUPS through the Web UI:

Remember to check the share printer option, otherwise CUPS will restrict access to the printer.

You can also add printers via the /etc/cups/printers.conf file while cupsd is stopped, if you also add a corresponding /etc/cups/ppd PPD file of the same name. Editing the printers.conf file manually is not recommended, and the options available are undocumented2. See this incomplete documentation from Apple for moree info on each option. Below is mine:

NextPrinterId 3

<DefaultPrinter printer>
PrinterId 1
UUID urn:uuid:fb026ac7-8613-3eb4-5b51-9749073f7704
AuthInfoRequired none
Info HP LaserJet 4100N
Location Office
MakeModel HP LaserJet 4100 Series hpijs pcl3, 3.21.2
DeviceURI socket://printer.home.arpa
State Idle
StateTime 1686858786
ConfigTime 1680818482
Type 8425500
Accepting Yes
Shared Yes
JobSheets none none
QuotaPeriod 0
PageLimit 0
KLimit 0
OpPolicy default
ErrorPolicy stop-printer
Attribute marker-colors none
Attribute marker-levels 27
Attribute marker-names Toner Cartridge HP C8061X
Attribute marker-types toner
Attribute marker-change-time 1686858786
</DefaultPrinter>

<Printer designjet>
PrinterId 2
UUID urn:uuid:b691f515-4a45-35da-5707-debfda29d917
Info HP DesignJet 650C
Location Office
MakeModel HP DesignJet 650C Foomatic/dnj650c (recommended)
DeviceURI socket://designjet.home.arpa
State Idle
StateTime 1683579922
ConfigTime 1671595356
Type 8450060
Accepting Yes
Shared Yes
JobSheets none none
QuotaPeriod 0
PageLimit 0
KLimit 0
OpPolicy default
ErrorPolicy stop-printer
</Printer>

Both printers I have configured are communicating via the AppSocket protocol via socket:// aka TCP port 9100 over the local network. I use pfSense running on a VM as my home router, which provides DNS resolution via .home.arpa, the recommended home DNS suffix, which is why they aren't IP addresses. I also recommend configuring your router to assign static IPs (through DHCP) to network devices like printers so that configurations like this can use the IP address. Alternatively, if your printer or network card is sophisticated enough (like the later EIO careds), you can use an mDNS address with a URI like dnssd://HP-LaserJet-4100n._pdl-datastream._tcp.local (optionally with ?uuid=). The problem with using mDNS for this is that now your printer will be advertising itself and will show up in the Mac print dialogue if it advertises _ipp._tcp., so I disable mDNS on the printer itself.

You will also need a PPD under /etc/cups/ppd that is either created via the CUPS UI wizard or uploaded manually (e.g. when fetched from openprinting.org). CUPS PPD files support extensions atop Adobe PPD version 4.33.

CUPS will eventually deprecate using printer drivers with PPDs, in favor of using a Printer Application (as described in A New Way to Print in Linux) or PAPPL. A PAPPL-based app is one that presents an IPP interface which accepts PDFs and then converts them (the same way that CUPS does internally) to the custom or raster format of the actual printer, and sends it over the appropriate interface. There are already printer apps for HP printers that use hplip (such as my HP LaserJet 4100n), hp-printer-app, for printers that use a GhostScript driver (such as my HP DesignJet 650c), ghostscript-printer-app, and for printers that use a GutenPrint driver, gutenprint-printer-app. I've yet to migrate to these printer apps but plan to do so, I've run into the following issues:

After the printer is configured, ensure you print a test page to ensure CUPS can communicate to the printer and the chosen driver works with your model.

Configuring Avahi

Once that's done, you'll need to generate your Avahi service config. It's not difficult to write on manually, the below is what I use for my HP LaserJet and is at /etc/avahi/services/AirPrint-printer.service:

<?xml version="1.0" ?>
<!DOCTYPE service-group  SYSTEM 'avahi-service.dtd'>
<service-group>
	<name replace-wildcards="yes">HP LaserJet 4100N</name>
	<service>
		<type>_ipp._tcp</type>
		<subtype>_universal._sub._ipp._tcp</subtype>
		<port>631</port>
		<txt-record>txtvers=1</txt-record>
		<txt-record>qtotal=1</txt-record>
		<txt-record>UUID=AFEB3961-F48A-499A-B6C5-605A723CECF4</txt-record>
		<txt-record>Transparent=T</txt-record>
		<txt-record>Binary=T</txt-record>
		<txt-record>TBCP=T</txt-record>
		<txt-record>kind=document,envelope</txt-record>
		<txt-record>Duplex=T</txt-record>
		<txt-record>URF=DM3</txt-record>
		<txt-record>rp=printers/printer</txt-record>
		<txt-record>note=Office</txt-record>
		<txt-record>product=(GPL Ghostscript)</txt-record>
		<txt-record>printer-state=3</txt-record>
		<txt-record>printer-type=0x409014</txt-record>
		<txt-record>pdl=application/octet-stream,application/pdf,application/postscript,application/vnd.cups-raster,image/gif,image/jpeg,image/png,image/tiff,image/urf,text/html,text/plain,application/vnd.adobe-reader-postscript,application/vnd.cups-pdf</txt-record>
	</service>
</service-group>

You can also use the airprint-generate.py script by Timothy Fontaine. To get airprint-generate.py running, you'll need to install some dependencies:

; sudo dnf install pip gcc clang libpython python3-devel cups-devel
; pip3 install wheel pycups

Then run the script and copy the output to /etc/avahi/services/.

The pdl must include image/urf to work with AirPrint, from Apple's documentation:

AirPrint devices don’t browse for all IPP printers—they browse only for the subset of IPP printers that support Universal Raster Format (URF).

The service for my HP DesignJet is very similar, but I made some changes (some necessary, some for curiosity's sake). I'll illustrate the changes I made:

I replaced the name with

	<name replace-wildcards="yes">HP DesignJet 650C</name>

The UUID with

		<txt-record>UUID=3C84F9A5-A870-475F-A758-43DDF9B290EC</txt-record>

Added a TLS record, changed kind to reflect a large format printer, toggled Duplex off, toggled Color on, and changed PaperMax and PaperCustom to illustrate that the printer can print to large formats:

		<txt-record>TLS=1.2</txt-record>
		<txt-record>kind=large-format,roll</txt-record>
		<txt-record>Color=T</txt-record>
		<txt-record>PaperMax=>isoC-A2</txt-record>
		<txt-record>PaperCustom=T</txt-record>

Changed rp to the printer name, added a ty field:

		<txt-record>rp=printers/designjet</txt-record>
		<txt-record>ty=HP DesignJet 650C</txt-record>

and omitted the printer-state and printer-type fields.

If you have a Mac, you can use the Discovery app to see mDNS services available on the local network; which is useful for debugging as this is what your iPhone sees when it searches for nearby AirPrint-enabled printers.

Discovery app showing mDNS services on my local network
Discovery app showing mDNS services on my local network

The important services are under _ipp._tcp., you should see one per Avahi service file. Expanding a service should display each txt-record option as a row.

If Printer Sharing is enabled in CUPS and Avahi is installed, an IPP service will be advertised via mDNS but it won't have the necessary TXT records for AirPrint which indicate what capabilities the printer has. You should disable Printer Sharing to avoid duplicate printers (one driver-less, one conventional Bonjour) showing on a Mac. More information on each TXT record is available in the Bonjour Printing specification, in Table 2 on page 17 for description records and in Table 3 on page 23 for capability records.

Once your Avahi service is configured, you should see your printer on your iPhone. Try printing a page of a PDF, if it prints successfully congratulations. If not,

Using Nginx as a CUPS UI proxy

If you'd like to make CUPS available on the standard HTTP/HTTPS port, you can install nginx:

; sudo dnf install nginx

For a TLS-enabled server, add this configuration to /etc/nginx/conf.d/cups.conf:

server {
    listen       80;
    listen       [::]:80;
    server_name  cups.home.arpa;
    root         /usr/share/nginx/html;

    return 301 https://$host$request_uri;
}

# Settings for a TLS enabled server.
server {
    listen       443 ssl http2;
    listen       [::]:443 ssl http2;
    server_name  cups.home.arpa;
    root         /usr/share/nginx/html;

    ssl_certificate "/etc/pki/nginx/server.crt";
    ssl_certificate_key "/etc/pki/nginx/private/server.key";
    ssl_session_cache shared:SSL:1m;
    ssl_session_timeout  10m;
    ssl_ciphers PROFILE=SYSTEM;
    ssl_prefer_server_ciphers on;

    location / {
        proxy_pass http://127.0.0.1:631/;
        proxy_set_header Host "127.0.0.1";
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forward-For $proxy_add_x_forwarded_for;
    }

    error_page 404 /404.html;
    location = /404.html {
    }

    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
    }
}

With a TLS-enabled server, you need to create some PKI and add the cert to both the server running CUPS and your laptop. The easiest way to set up PKI is using cfssl. By setting server_name to cups.home.arpa, a DNS alias I added in my router (pfSense) which points to the same IP that DHCP assigns to the VM, I'm able to host multiple web interfaces for different sites on the same VM.

Why print in 2023?

Aside from the ocassional form, I often print recipes from the NYT Cooking app. Their iOS app produces beautiful and readable print-outs in crisp black-and-white serif text, with ingredients on the left-hand column and steps on the right. Printing recipes allows me to dirty my hands cooking without needing to constantly unlock my phone.


  1. For more information about mDNS, see the book Zero Configuration Networking: The Definitive Guide↩︎

  2. CUPS parses the printers.conf file in the cupsdLoadAllPrinters routine, which contains the names of the options. man printers.conf notes: "The name, location, and format of this file are an implementation detail that will change in future releases of CUPS." ↩︎

  3. Adobe no longer hosts a copy of the spec, but MIT hosts a copy of several Adobe tech notes; which can be accessed by replacing the file name in the URL with the orignal Adobe file name e.g. 5003.PPD_Spec_v4.3.pdf↩︎


See comments on Hacker News.