Anders Tall

AirPrint on macOS: HP LaserJet P3015dn + MacBook CUPS

6 min read

Tested on: MacBook Pro, macOS 26.6.2, with an HP LaserJet P3015dn on the LAN. Private IPs are examples; replace them with your own.

Same printer, another OS: Linux · macOS · Windows

Corrections since recording: The video uses a local sudo helper (cat ~/.macos_sudo | sudo -S) so recording would not stall. The text below uses interactive sudo; do not copy the password-file pattern. The sleep, reboot and ipptool checks are also newer than the video.

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 a MacBook on the same LAN can print to it.

The fix is macOS CUPS. It speaks the printer’s PCL5 on one side and AirPrint over Bonjour on the other. This page is the why and the exact commands; the videos show the same steps.

The printer is already on the network

The P3015dn (product number CE528A) sits at 192.168.2.83. Its JetDirect advertises IPP, but the TXT record has no URF and the pdl list has no image/urf, and it answers IPP 1.1. macOS’s everywhere driver wants IPP 2.0 and fails with:

server-error-version-not-supported

So we do not use IPP Everywhere. We use the standard socket backend on raw port 9100 and a PCL5 PPD. The P3015dn speaks PCL5, and macOS has the generic HP LaserJet Series PCL 4/5 PPD built in.

The bridge: macOS CUPS

The MacBook (192.168.2.37, macOS 26.6.2) owns the queue. The reference queue is AirPrintLaserJet3015Mac; the device URI is socket://192.168.2.83:9100; the driver is drv:///sample.drv/laserjet.ppd.

sudo lpadmin -p AirPrintLaserJet3015Mac -E \
  -v socket://192.168.2.83:9100 \
  -m drv:///sample.drv/laserjet.ppd

lpstat -t
lp -d AirPrintLaserJet3015Mac /usr/share/cups/data/testprint
lpstat -o

Run the sudo commands interactively. The video used a local helper so the recording would not stall on a password prompt; that helper is a production detail, not a pattern to copy. Never store a sudo password in a readable file.

The test page is the first cut: if it prints, the macOS queue is real.

Watch on YouTube →

Share it over Bonjour, then add the AirPrint TXT

Sharing the queue makes macOS CUPS advertise it, but that was not enough here. The PCL5 queue’s TXT record lacks URF/image/urf and the airprint keys, so the iPhone did not list it as an AirPrint printer:

sudo cupsctl --share-printers
sudo cupsctl BrowseLocalProtocols=dnssd
sudo lpadmin -p AirPrintLaserJet3015Mac -o printer-is-shared=true
lpstat -l -p AirPrintLaserJet3015Mac
perl -e "alarm 8; exec @ARGV" dns-sd -B _ipp._tcp local

dns-sd listed the CUPS service as:

AirPrintLaserJet3015Mac @ MacBook Pro

That is the Mac’s computer name; the static bridge deliberately uses the shorter AirPrintLaserJet3015Mac @ MacBook. The iPhone still ignored the CUPS instance. The fix is a static dns-sd -R advertisement with a unique instance name, run as a launchd daemon so it survives reboot:

/Library/LaunchDaemons/nu.tall.airprint-bridge.plist:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key><string>nu.tall.airprint-bridge</string>
  <key>ProgramArguments</key>
  <array>
    <string>/usr/bin/dns-sd</string>
    <string>-R</string>
    <string>AirPrintLaserJet3015Mac @ MacBook</string>
    <string>_ipp._tcp,_universal</string>
    <string>local</string>
    <string>631</string>
    <string>txtvers=1</string>
    <string>qtotal=1</string>
    <string>rp=printers/AirPrintLaserJet3015Mac</string>
    <string>ty=HP LaserJet P3015dn</string>
    <string>pdl=application/pdf,application/postscript,image/jpeg,image/png,image/pwg-raster,image/urf</string>
    <string>URF=V1.4,CP1,W8,PQ3-4-5,RS300,DM1,FN3</string>
    <string>Color=F</string>
    <string>Duplex=T</string>
    <string>airprint=1.8</string>
    <string>ipp-features-supported=airprint-1.8</string>
  </array>
  <key>RunAtLoad</key><true/>
  <key>KeepAlive</key><true/>
</dict>
</plist>

Save the XML block above as airprint-bridge.plist in the current directory, then install and start it:

sudo install -o root -g wheel -m 644 airprint-bridge.plist \
  /Library/LaunchDaemons/nu.tall.airprint-bridge.plist
sudo launchctl bootout system/nu.tall.airprint-bridge 2>/dev/null || true
sudo launchctl bootstrap system \
  /Library/LaunchDaemons/nu.tall.airprint-bridge.plist
sudo launchctl list | grep airprint

The instance is deliberately AirPrintLaserJet3015Mac @ MacBook, not the bare queue name, so it cannot collide with AirPrintLaserJet3015 @ Linux or master’s CUPS instance. More importantly, the registration type is _ipp._tcp,_universal: iOS browses the _universal._sub._ipp._tcp subtype for AirPrint. Without it the printer shows up under “Other printers” but cannot be selected. The job goes to macOS CUPS, which sends PCL5 to the JetDirect on port 9100. The static record only advertises the queue; CUPS still does the printing.

Watch on YouTube →

The perl -e "alarm 8; exec @ARGV" wrapper only bounds the dns-sd browse so a recording does not hang; it is not needed interactively.

Verify the advertised IPP attributes from another machine (install cups-ipp-utils on Linux if needed):

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

Check for URF, image/urf, pwg-raster, and duplex support. The static record advertises them; the queue must actually accept them.

Keep the Mac awake

A sleeping MacBook does not answer Bonjour or IPP, so the iPhone cannot print. For a print server, prevent sleep on power. On current MacBooks the setting is under System Settings → Battery → Options (older macOS: Energy Saver). A version-independent command is:

sudo pmset -c sleep 0

Use caffeinate -s while testing if you do not want to change the setting. The lid must stay open; a closed MacBook sleeps and cannot answer Bonjour or IPP.

On the iPhone: Print → choose AirPrintLaserJet3015Mac @ MacBook → Print. The filmed segment shows the sheet and the paper path. The MacBook logs the job; the P3015dn prints.

Watch on YouTube →

Reboot check

The point of the launchd daemon is that the advertisement survives a reboot. Restart the Mac, then verify:

sudo launchctl list | grep airprint
perl -e "alarm 8; exec @ARGV" dns-sd -B _ipp._tcp local
lp -d AirPrintLaserJet3015Mac /usr/share/cups/data/testprint

Print from the iPhone after the reboot as well. If the queue is gone, check that the plist is in /Library/LaunchDaemons, that it is owned by root, and that launchctl print system/nu.tall.airprint-bridge shows a running process.

Troubleshooting

everywhere fails with server-error-version-not-supported. The P3015dn is IPP 1.1. Use the socket backend and a PCL5 PPD, not IPP Everywhere.

No P3010/3015 PPD in lpinfo -m. macOS 26.6 ships only generic drivers here. The HP LaserJet Series PCL 4/5 PPD is enough for basic printing.

The queue prints but the iPhone does not see it. Check cupsctl --share-printers, lpstat -l -p AirPrintLaserJet3015Mac, and dns-sd -B _ipp._tcp local. Then inspect the TXT record for URF, image/urf, pdl, and the instance name. The static dns-sd -R record above is what supplies the AirPrint-specific TXT; the MacBook and iPhone must be on the same LAN, and guest/client isolation blocks Bonjour.

The job is slow or garbled. The generic PCL5 PPD is a lowest common denominator; check the media size and the driver. The exact P3010 PPD would be better if HP ships one for your macOS version.

Security

The MacBook shares the queue on the LAN; do not expose port 631 or Bonjour to the internet. Use interactive sudo; never keep a readable password file. The P3015dn’s embedded web server is old (TLS 1.0/SSLv3); treat the printer as a trusted LAN device.

The series

The playlist: AirPrint: macOS →

Three short videos: add the P3015dn to macOS CUPS, share it over Bonjour, and print from an iPhone.

#airprint #macos #macbook #cups #bonjour #dnssd #hp-laserjet #p3015dn #pcl5 #printing #self-hosting