Přeskočit na hlavní obsah

Sharing a USB Brother Printer on Android via Linux and CUPS

·5 min

You have a Brother printer plugged into a Linux box on your home network, and you want to print from your Android phone without fiddling with cables or third-party apps. This is doable with CUPS, nginx, and Avahi — but there are several non-obvious pitfalls along the way. This post covers the full setup, including the debugging steps that actually taught me what was going wrong.

Architecture #

Android
  │  mDNS discovery (_ipps._tcp)
  ▼
Avahi (advertises printserver.local:631)
  │
nginx (port 631, TLS terminated, cert trusted by Android)
  │
CUPS (localhost:6310)
  │
Brother printer (USB)

nginx sits between Android and CUPS to handle TLS properly. Android’s Mopria print service requires a trusted HTTPS connection for automatic discovery to work.

Prerequisites #

  • A Linux machine with the printer connected via USB
  • A local CA certificate already installed as trusted on your Android device
  • nginx, cups, avahi-daemon, avahi-utils installed
sudo apt install cups nginx avahi-daemon avahi-utils

Step 1: Configure CUPS #

Edit /etc/cups/cupsd.conf. The key change is to bind CUPS to localhost only on a non-standard port — nginx will own port 631 externally.

Listen 127.0.0.1:6310
Listen /run/cups/cups.sock

Browsing No
BrowseLocalProtocols none

DefaultAuthType Basic
WebInterface Yes

<Location />
  Allow @LOCAL
  Order allow,deny
</Location>

<Location /admin>
  AuthType Default
  Require user @SYSTEM
  Order allow,deny
</Location>

Setting BrowseLocalProtocols none disables CUPS’s built-in mDNS registration — we’ll control that ourselves via Avahi to get the hostname right.

sudo systemctl enable --now cups

Step 2: Add the printer #

Detect the printer:

sudo lpinfo -v

You should see something like:

direct usb://Brother/DCP-1510%20series?serial=XXXXXXXX

Find the driver:

sudo lpinfo --make-and-model "Brother DCP-1510" -m

Output:

drv:///brlaser.drv/br1510.ppd Brother DCP-1510 series, using brlaser v6

Add the printer and set it as default:

sudo lpadmin -p Brother-DCP-1510 -E \
  -v 'usb://Brother/DCP-1510%20series?serial=XXXXXXXX' \
  -m 'drv:///brlaser.drv/br1510.ppd' \
  -D 'Brother DCP-1510' \
  -L 'printserver'

sudo lpoptions -d Brother-DCP-1510
sudo cupsctl --share-printers

Verify:

lpstat -p

Step 3: TLS certificate #

Android won’t trust a self-signed certificate unless your local CA is installed on the device. If you already have a CA, generate a certificate for the print server with the correct Subject Alternative Names:

subjectAltName=DNS:printserver.home,DNS:printserver.local

printserver.local is critical — Avahi advertises your machine under the .local domain, and the TLS certificate must match that name or Android will reject the connection silently.

Copy the certificate and key to /etc/nginx/certs/.

Step 4: nginx as IPPS reverse proxy #

Create /etc/nginx/sites-available/cups-ipps:

server {
    listen 631 ssl;
    server_name printserver.home printserver.local;

    ssl_certificate /etc/nginx/certs/printserver.crt;
    ssl_certificate_key /etc/nginx/certs/printserver.key;

    ssl_protocols TLSv1.2 TLSv1.3;

    access_log /var/log/nginx/cups.access.log;
    error_log  /var/log/nginx/cups.error.log;

    location / {
        proxy_pass         http://127.0.0.1:6310/;
        proxy_set_header   Host localhost:6310;
        proxy_set_header   X-Forwarded-Proto https;
        proxy_http_version 1.1;
        proxy_buffering    off;
        proxy_read_timeout 300;
    }
}

Enable and reload:

sudo ln -s /etc/nginx/sites-available/cups-ipps /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

Verify IPPS is working:

curl -sk -o /dev/null -w "%{http_code}" \
  --cacert /path/to/rootCA.crt \
  https://printserver.local:631/printers/Brother-DCP-1510
# → 200

Step 5: Avahi mDNS announcement #

Avahi service files do not support a custom <host> element, so we use avahi-publish via a systemd unit instead. This lets us control exactly what hostname and port Android sees in the mDNS response.

Create /etc/systemd/system/avahi-cups.service:

[Unit]
Description=Avahi mDNS announcement for Brother DCP-1510
After=avahi-daemon.service network-online.target
Requires=avahi-daemon.service

[Service]
ExecStart=/usr/bin/avahi-publish -s "Brother DCP-1510" _ipps._tcp 631 \
  "txtvers=1" \
  "qtotal=1" \
  "rp=printers/Brother-DCP-1510" \
  "ty=Brother DCP-1510 series" \
  "adminurl=https://printserver.local:631/printers/Brother-DCP-1510" \
  "note=printserver" \
  "priority=0" \
  "product=(DCP-1510)" \
  "pdl=application/pdf,application/postscript,image/jpeg,image/png,image/pwg-raster,image/urf" \
  "UUID=your-uuid-here" \
  "TLS=1.2" \
  "URF=V1.4,CP1,W8,PQ4,RS600,FN3" \
  "mopria-certified=1.3" \
  "Copies=T" \
  "printer-type=0x1046"
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now avahi-cups

The UUID can be any stable UUID. Use uuidgen to create one.

Step 6: Restrict Avahi to the LAN interface #

If your machine runs Docker, Avahi will by default reflect mDNS across Docker bridge interfaces. This floods the network with announcements containing Docker IP addresses (172.x.x.x), which confuses Android.

Edit /etc/avahi/avahi-daemon.conf and set:

[reflector]
enable-reflector=no

[server]
allow-interfaces=eth0   # replace with your actual LAN interface
sudo systemctl restart avahi-daemon

Check the interface name with ip link if you’re unsure.

Verification #

Watch mDNS traffic while triggering a search on Android:

sudo tcpdump -i eth0 -n port 5353 -v

You should see Android (e.g., 192.168.1.x) querying _ipps._tcp.local and your server responding with an SRV record pointing to printserver.local.:631 and an A record with your machine’s IP. Then check nginx logs for an incoming connection.

What I learned the hard way #

CUPS mDNS vs. Avahi: CUPS registers its own mDNS entry via Avahi and always uses hostname.local as the SRV target. If you terminate TLS in nginx but let CUPS handle mDNS, Android connects to hostname.local:631, gets your nginx cert — and if the cert doesn’t include hostname.local in its SAN, TLS fails silently with “no printer found.”

Docker and Avahi reflector: Enabling enable-reflector=yes without restricting interfaces causes Avahi to forward mDNS announcements onto Docker bridge networks, and vice versa. The announcements come back with Docker IPs, which Android caches and then can’t connect to.

Android also uses SNMP: Beyond mDNS, Android’s print service broadcasts SNMP requests on UDP 161 looking for printers. CUPS doesn’t respond to SNMP. This is why “search for printers” sometimes finds nothing even when mDNS is working — the SNMP sweep returns empty and the app gives up. Manual entry via ipps://printserver.local:631/printers/Brother-DCP-1510 always works once TLS is correctly configured.

Port conflict: If you want nginx on port 631, CUPS cannot also bind to 0.0.0.0:631. Binding CUPS to 127.0.0.1:6310 avoids the conflict while keeping things clean.