Anders Tall

AirPrint on Linux: HP LaserJet P3015dn + Debian CUPS

8 min read

Tested on: Debian 13, CUPS 2.4.10 and Avahi 0.8, with an HP LaserJet P3015dn on the LAN. The private IPs are examples; replace them with your own.

Same printer, another OS: Linux · macOS · Windows

Corrections since recording: The video shows the Avahi file, systemctl reload avahi-daemon, avahi-browse, and lpstat. This text adds cupsctl --share-printers, OptionDuplex=True, and the ipptool checks. Use the text for a fresh setup; the video is the recorded demo.

An HP LaserJet P3015dn is a network printer. It has Ethernet, automatic duplex, and a built-in JetDirect. What it does not have is AirPrint — so the iPhone’s print sheet ignores it, even though every Linux box on the LAN can print to it.

The fix is a small Debian VM running CUPS. It speaks the printer’s raw socket protocol on one side and AirPrint on the other. This page is the why; the video shows the recorded run, and the text below includes the corrections.

The printer is already on the network

The P3015dn (product number CE528A) sits at 192.168.2.83. Its JetDirect advertises IPP over mDNS:

HP LaserJet P3010 Series [333F03]  _ipp._tcp
  pdl=application/postscript,application/vnd.hp-PCL,application/vnd.hp-PCLXL

That is a perfectly good network printer service. It is not AirPrint, because the pdl list has no image/urf and the TXT record has no URF. iOS does not fall back to PostScript or PCL; it wants the AirPrint raster (URF) or a driverless IPP printer that speaks PDF/PWG-Raster.

The bridge: CUPS

A Debian 13 VM (airprint-linux-lab, 192.168.2.113, CUPS 2.4.10, Avahi 0.8) talks to the JetDirect on raw port 9100 and owns the driver. The queue is called AirPrintLaserJet3015; the driver is the p3010-series PCL3/hpcups PPD (it sends PCL5 data to the printer).

PPD="hplip:0/ppd/hplip/HP/hp-laserjet_p3010_series-pcl3.ppd"
URI="socket://192.168.2.83:9100"

sudo apt install cups cups-client cups-filters cups-ipp-utils hplip avahi-daemon avahi-utils
sudo lpadmin -p AirPrintLaserJet3015 -E -v "$URI" -m "$PPD" -o OptionDuplex=True
sudo systemctl enable --now cups avahi-daemon
lpstat -t
lp -d AirPrintLaserJet3015 /usr/share/cups/data/testprint

The p3010-series PPD is the correct family driver: HPLIP’s list covers the P3015 and P3011. hp-laserjet_3015 belongs to the HP LaserJet 3015 All-in-One, a different printer with fax. That is why lpinfo -m | grep -i 3015 can point you at the wrong PPD. HPLIP also ships a PostScript version (hp-laserjet_p3010_series-ps.ppd); test it if the PCL5 path gives trouble. The test page is the first cut: if it prints, the bridge is real.

Watch on YouTube →

The part that actually made the iPhone see it: one small Avahi file

Sharing the CUPS queue is necessary, but it was not enough on this setup. CUPS 2.4.10 advertises the queue over DNS-SD with URF and image/urf:

sudo lpadmin -p AirPrintLaserJet3015 -o printer-is-shared=true
sudo cupsctl --share-printers
avahi-browse -rt _ipp._tcp

avahi-browse showed the queue, yet the iPhone still did not list it. CUPS 2.4.10 already advertises _universal._sub._ipp._tcp and supplies image/urf through the Debian patch, but its TXT record for this PCL5 queue did not make the queue selectable here.

The static Avahi service is a second record alongside CUPS’s own advertisement, not a replacement for it. That is why the instance name must be unique. It changes several things at once: the instance name, the TXT record, and the absence of CUPS’s TLS/UUID/air fields. That makes it a working fix, not a minimal proof of which single key iOS requires. If you want to know whether the extra TXT keys matter, remove them and retest from the iPhone; avahi-browse -rt _ipp._tcp shows what is still advertised.

The full file is in The static Avahi service; create it there first, then run:

cat /etc/avahi/services/airprint.service
sudo systemctl reload avahi-daemon
avahi-browse -rt _ipp._tcp

The relevant fields are:

AirPrintLaserJet3015 @ Linux  _ipp._tcp
  pdl=application/pdf,...,image/urf
  URF=V1.4,CP1,W8,PQ3-4-5,RS300,DM1,FN3
  airprint=1.8
  ipp-features-supported=airprint-1.8
  Duplex=T
  rp=printers/AirPrintLaserJet3015

URF and image/urf describe what the queue accepts, not what a particular filter can produce. The static file does not create a second print queue; it advertises the existing CUPS queue. The iPhone lists that queue, not the JetDirect.

Verify the IPP side from another Linux host (the cups-ipp-utils package provides ipptool):

ipptool -tv ipp://192.168.2.113:631/printers/AirPrintLaserJet3015 \
  /usr/share/cups/ipptool/get-printer-attributes.test

ipptool reads IPP attributes, not the DNS-SD TXT record. Look for:

CUPS may answer ipp-features-supported = ipp-everywhere,... without any airprint-* value. That is normal and does not mean the queue is broken; it is a different attribute from the DNS-SD TXT keys.

Watch on YouTube →

A few TXT fields are worth reading correctly:

On the iPhone: Print → choose AirPrintLaserJet3015 @ Linux → Print. The job goes to CUPS over IPP, CUPS rasterizes it with the P3010 driver, and sends PCL to the JetDirect on port 9100. The printer does the rest.

The P3015dn can print double-sided. OptionDuplex=True makes the queue agree with the static TXT’s Duplex=T and the URF’s duplex mode; the iPhone offered double-sided before that setting too. If it produces a duplex page, that is the proof that the whole chain — not just a one-page demo — is real.

Watch on YouTube →

Make it survive a reboot

A print server that only works until the next reboot is not finished.

systemctl is-enabled cups avahi-daemon
ls -l /etc/avahi/services/airprint.service
sudo systemctl restart cups avahi-daemon
lpstat -t
avahi-browse -rt _ipp._tcp

Watch on YouTube →

Troubleshooting

The queue appears, but the job fails. Check the CUPS error log and the queue state (lpstat -l -p AirPrintLaserJet3015, lpstat -o). A wrong PPD, a missing filter, or a URF that promises something the driver cannot deliver will show up here. The printer’s own JetDirect service is not the queue; make sure the iPhone selected the CUPS queue.

The queue does not appear. avahi-browse -rt _ipp._tcp on the server tells you whether CUPS advertises it at all. Check that the queue is shared and that cupsctl shows _share_printers=1 and BrowseLocalProtocols=dnssd. Then check the network: mDNS does not cross VLANs or client isolation without a reflector, and UDP 5353 plus TCP 631 must be allowed on the LAN.

The printer is visible but the job is slow or garbled. The P3010-series PPD is generic; it lists more trays than the P3015dn has. Check the driver choice and the media size. The exact 3015 PPD is an alternative if the generic one misbehaves.

hpfax errors in the log. The P3015dn has no fax; the hplip fax plugin logs an error at startup. It is unrelated to printing.

The JetDirect web UI will not open in a modern browser. The embedded server is old (TLS 1.0/SSLv3). That is another reason to leave it alone and let CUPS do the modern protocol work.

Security

CUPS on a LAN is fine; CUPS on the internet is not. Do not forward port 631. cupsctl --share-printers writes Allow @LOCAL; do not add --remote-any (CUPS documents that as allowing internet clients). Verify cupsd.conf and keep the firewall scoped to the LAN. The /admin UI already requires an authenticated @SYSTEM user. cups-browsed is not needed for this queue; if it is installed for another printer, keep it patched and do not expose it.

The JetDirect’s old TLS is a good reminder: the printer is a trusted LAN device, not an internet-facing one.

The static Avahi service

This is the file that worked on the lab VM. Put it in /etc/avahi/services/airprint.service, reload avahi-daemon, and verify with avahi-browse -rt _ipp._tcp. The service name is deliberately AirPrintLaserJet3015 @ Linux: the queue is still AirPrintLaserJet3015, but a unique instance name avoids colliding with another host’s CUPS or bridge advertisement.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE service-group SYSTEM "avahi-service.dtd">
<service-group>
  <name>AirPrintLaserJet3015 @ Linux</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>rp=printers/AirPrintLaserJet3015</txt-record>
    <txt-record>ty=HP LaserJet P3015dn</txt-record>
    <txt-record>pdl=application/pdf,application/postscript,image/jpeg,image/png,image/pwg-raster,image/urf</txt-record>
    <txt-record>URF=V1.4,CP1,W8,PQ3-4-5,RS300,DM1,FN3</txt-record>
    <txt-record>Color=F</txt-record>
    <txt-record>Duplex=T</txt-record>
    <txt-record>airprint=1.8</txt-record>
    <txt-record>ipp-features-supported=airprint-1.8</txt-record>
  </service>
</service-group>

The pdl list has to include image/urf. URF describes the raster formats the queue accepts; it is not a promise about what a particular CUPS filter can produce. If you use another queue name, change rp and the service name to match; iOS prints to the CUPS queue named in rp.

The series

The playlist: AirPrint: Linux →

Four short videos: add the P3015dn to CUPS, make it AirPrint-capable, print from the iPhone, and keep it working after a reboot.

#airprint #cups #avahi #dnssd #debian #hp-laserjet #p3015dn #jetdirect #printing #self-hosting