From USB Printer to Browser-Based Network Scanner

 

The original goal sounded simple: the Brother printer already worked through CUPS and I set that up as part of my LPIC 1 studies, so scanning should also be available from the network. In practice, the printer and scanner parts of a multifunction device are handled by different Linux subsystems. Printing was already solved through CUPS as part of LPIC 1. Scanning required a separate path through SANE, the Brother scanner backend, and finally a way to expose the scanner to Windows clients without installing scanner software on every machine.

The device in question was a Brother DCP-7055 connected by USB to the Ubuntu VM Test which was also my printer server. The working CUPS printer queue was named brother7055, and printing was already functional. The Ubuntu host had the management LAN address 192.168.10.123, and later we also confirmed that it had a second address, 192.168.178.123.


The network design matters here. VLAN 10 is the management network, and access into this VLAN is controlled through OPNsense. Remote access is handled through WireGuard VPN, so the intended access path is not “open service on the home network,” but rather:

Remote client
  -> WireGuard VPN
  -> OPNsense
  -> VLAN 10
  -> Ubuntu host Test
  -> scanner web interface

This is important because we did not want to solve this by installing scanner software on every Windows client. CUPS already gave us a clean network-printing experience. For scanning, we wanted a similar operational result: open a browser, scan a document, download the result.

Starting Point: Printing Worked, Scanning Did Not

The first check was to identify the real CUPS queue and the printer model. An earlier assumption about a queue named OFFICE was wrong. The actual queue was:

lpstat -a

Output:

brother7055 accepting requests since Mon 01 Jun 2026 04:42:34 PM CEST

Then we checked the queue details:

lpstat -v brother7055
sudo grep -iE 'NickName|ModelName|Product|Manufacturer|Brother' /etc/cups/ppd/brother7055.ppd

The result confirmed the exact device and connection:

device for brother7055: usb://Brother/DCP-7055?serial=E69739M1N441270
*Product: "(DCP-7055)"
*Manufacturer: "Brother"
*ModelName: "Brother DCP-7055"
*ShortNickName: "Brother DCP-7055"
*NickName: "Brother DCP-7055, using brlaser v6"
*1284DeviceID: "MFG:Brother;CMD:PJL,HBP;MDL:DCP-7055;CLS:PRINTER;CID:Brother Laser Type1;"

This showed that printing was handled through the open brlaser driver. That solved printing only. It did not provide scanner support.

The scanner packages initially installed on the system were generic SANE components and sane-airscan, but no Brother scanner backend:

libsane-common
libsane1
printer-driver-brlaser
printer-driver-ptouch
sane-airscan
sane-utils

A USB check showed that Linux could physically see the Brother multifunction device:

lsusb

Relevant output:

Bus 002 Device 005: ID 04f9:0248 Brother Industries, Ltd DCP-7055 scanner/printer

But scanimage -L still failed:

scanimage -L

Output:

No scanners were identified.

Running the low-level scanner detection as root gave the key diagnostic result:

sudo sane-find-scanner
sudo scanimage -L

Output:

found possible USB scanner (vendor=0x04f9, product=0x0248) at libusb:002:005

But SANE still did not list a usable scanner:

No scanners were identified.

This meant the hardware was visible, but SANE did not yet have the correct Brother backend for this model.


  

Installing the Brother Scanner Backend

The missing component was Brother’s brscan4 scanner driver. We installed two packages:

brscan4-0.4.10-1.amd64.deb
brother-udev-rule-type1-1.0.2-0.all.deb

These two packages serve different purposes.

brscan4 provides the Brother scanner backend for SANE. This is what allows SANE tools such as scanimage to communicate with the DCP-7055 as a scanner.

brother-udev-rule-type1 installs USB device rules. These rules make the USB scanner accessible to a normal user without requiring every scan command to run as root.

The installation was performed with:

sudo dpkg -i --force-all ./brscan4-0.4.10-1.amd64.deb
sudo dpkg -i ./brother-udev-rule-type1-1.0.2-0.all.deb
sudo apt -f install

Then udev rules were reloaded:

sudo udevadm control --reload-rules
sudo udevadm trigger

After that, scanimage -L worked:

scanimage -L
sudo scanimage -L

Output:

device `brother4:bus2;dev1' is a Brother DCP-7055 USB scanner
device `brother4:bus2;dev1' is a Brother DCP-7055 USB scanner

A real test scan was then created:

scanimage --format=png > test-scan.png

The command completed successfully. The only messages were normal rounding messages from the scanner backend:

scanimage: rounded value of br-x from 211.9 to 211.881
scanimage: rounded value of br-y from 355.6 to 355.567

At this point, the scanner worked locally on Ubuntu. The next problem was network access.

Why Not Just Use CUPS?

CUPS handles printing, not scanning. A printer queue can be shared cleanly across the network, and Windows can use that printer without installing Brother’s full software stack on every client.

Scanning is different. The scanner side uses SANE on Linux. A classic solution would be saned, the SANE network daemon. That would expose the scanner over the network, but Windows clients would still need software that understands SANE, such as NAPS2.

That did not fit the goal.

The goal was:

No Brother scanner driver on every Windows client.
No NAPS2 installation on every Windows client.
Use the scanner from a browser if possible.
Keep CUPS printing untouched.

The better fit was scanservjs.

Choosing scanservjs

scanservjs is a web frontend for SANE scanners. It runs on the Linux machine that has access to the scanner, and clients use it through a browser.

The resulting architecture is simple:

Windows client
  -> browser
  -> http://192.168.10.123:8080
  -> scanservjs
  -> SANE
  -> brscan4
  -> USB
  -> Brother DCP-7055

This gives us the practical behavior we wanted: Windows machines can scan without installing scanner-specific client software.

Handing the Setup to Codex

At this point, Codex was used as the execution agent on the Ubuntu host. The instruction was not to reconfigure CUPS and not to use Docker unless necessary. The task was to install scanservjs through the official upstream Debian bootstrap path, verify the scanner, verify the service, and report back with the operational state.

The relevant instruction was:

Task: Finish scanner web access setup on Ubuntu host Test.

Context:
- The Brother DCP-7055 is connected by USB.
- Printing already works via CUPS queue brother7055.
- Local scanning now works on Ubuntu.
- scanimage -L returns:
  device brother4:bus2;dev1 is a Brother DCP-7055 USB scanner
- A test scan with:
  scanimage --format=png > test-scan.png
  completed successfully.
- Goal: make scanning usable from Windows machines without installing Brother/NAPS2/scanner software on every Windows client.
- Preferred solution: install and expose scanservjs as a web scan interface on the Ubuntu server.

Codex first checked the local environment and confirmed that curl was not installed:

which curl || true

No path was returned.

It also checked whether scanservjs or curl were already installed:

dpkg -l scanservjs curl 2>/dev/null || true

The result showed that curl was not installed.

A scanner check initially returned no output until permission for scanner access was approved. After that, Codex ran:

scanimage -L

Output:

device `brother4:bus2;dev1' is a Brother DCP-7055 USB scanner

Codex also confirmed it was running as root:

whoami

Output:

root

Then it performed a real scan test:

scanimage --format=png > /tmp/test-scan.png && file /tmp/test-scan.png

The scan completed successfully and produced a valid PNG:

/tmp/test-scan.png: PNG image data, 1648 x 2783

Installing curl and scanservjs

The first manual attempt to run the scanservjs bootstrap script had failed because the system did not have curl. Even when the script was downloaded with wget, the script itself called curl internally:

bootstrap.sh: line 40: curl: command not found
Unable to find release "latest"

Codex repaired this correctly by installing curl through apt, not Snap:

apt-get update
apt-get install -y curl

Then Codex ran the upstream bootstrap installer:

curl -fsSL https://raw.githubusercontent.com/sbs20/scanservjs/master/bootstrap.sh | bash -s -- -v latest

The installer resolved the stable release:

Found asset: https://github.com/sbs20/scanservjs/releases/download/v3.1.0/scanservjs_3.1.0-1_all.deb

The package installed successfully.

Service Verification

Codex then checked the systemd service:

systemctl status scanservjs --no-pager --full

The result showed that the service was enabled and running:

scanservjs.service - scanservjs
Loaded: loaded (/usr/lib/systemd/system/scanservjs.service; enabled; preset: enabled)
Active: active (running)

The service log reported that scanservjs had started listening on port 8080:

scanservjs started listening: https://:::8080

The installed package version was checked with:

dpkg-query -W -f='${Version}\n' scanservjs

Output:

3.1.0

The firewall state was checked:

ufw status verbose

Output:

Status: inactive

A local HTTP check confirmed that the web interface responded:

curl -I http://127.0.0.1:8080

The result was:

HTTP/1.1 200 OK

The host IP addresses were checked:

hostname -I

Output:

192.168.10.123 192.168.178.123

The listening port was checked with:

ss -ltnp | grep ':8080'

Output:

LISTEN 0 511 *:8080 *:* users:(("node",pid=1292908,fd=24))

This confirmed that the service was listening on all interfaces, not only on 192.168.10.123.

Final State

The final working state was:

Host: Test
Device: Brother DCP-7055 USB scanner/printer
Printing: CUPS queue brother7055
Scanner backend: Brother brscan4 through SANE
Scanner device: brother4:bus2;dev1
scanservjs version: 3.1.0
Service: scanservjs.service
Service status: enabled and active
Port: TCP 8080
Binding: *:8080
Management LAN URL: http://192.168.10.123:8080
Secondary host IP: 192.168.178.123
UFW: inactive
CUPS queue modified: no

From a Windows client on the management LAN, the scanner web interface is reachable at:

http://192.168.10.123:8080

This gives Windows browser-based scanning without installing Brother scanner software or NAPS2 on every Windows machine.

 

Security Notes

The important security point is that scanservjs currently listens on all interfaces:

*:8080

The host has at least two IP addresses:

192.168.10.123
192.168.178.123

So the web interface may be reachable not only through the management VLAN address, but also through the secondary address, depending on network routing and client location.

At the same time, enabling UFW is not automatically the best next step. The main network boundary is already OPNsense. VLAN 10 is protected by OPNsense, and remote access into VLAN 10 is intended to happen only through WireGuard VPN.

The cleaner model is:

OPNsense = main firewall and VLAN boundary
WireGuard = controlled remote access path into VLAN 10
Ubuntu host firewall = optional, not required for normal inter-VLAN control
scanservjs binding = should be reviewed because it listens on all interfaces

There is one limitation to remember: OPNsense controls routed traffic between networks. It does not normally filter direct host-to-host traffic inside the same subnet. So any client already inside the same Layer 2 management LAN may be able to reach 192.168.10.123:8080.

For the current lab, this is acceptable and the goal as a controlled management service becaus VLAN 10 only contains trusted administration systems. Long-term, the better hardening step is not necessarily “turn on UFW,” but rather to reduce the exposure of the service if possible.

Recommended follow-up hardening options:

1. Check whether scanservjs can bind only to 192.168.10.123 instead of *:8080.
2. Remove the secondary 192.168.178.123 address from Test if it is no longer needed.
3. Keep OPNsense as the central policy point for inter-VLAN and VPN access.
4. Consider host-level outbound control separately if the concern is applications on Ubuntu phoning home.
5. Add authentication or a reverse proxy only if the scanner interface needs broader access than the trusted management LAN.

Lessons Learned

The main lesson is that multifunction printers are not one single Linux subsystem.

Printing worked through:

CUPS -> brlaser -> Brother DCP-7055

Scanning required:

SANE -> brscan4 -> Brother DCP-7055

Network access for scanning then required an additional publishing layer:

scanservjs -> browser-based scan interface

The second lesson is that detection has layers. lsusb and sane-find-scanner proved that the hardware was visible. scanimage -L proved whether SANE could actually use it. Before installing brscan4, Linux could see the USB device but could not use it as a scanner. After installing brscan4 and the Brother udev rules, SANE detected the scanner correctly.

The third lesson is that using Codex as an execution agent works best when the task is scoped clearly. In this case, Codex was told not to touch the working CUPS printer queue, not to introduce Docker, and to report back with concrete verification: scanner detection, test scan, installed version, service status, listening port, URL, and security notes.

The result is a clean split:

CUPS handles printing.
SANE and brscan4 handle local scanning.
scanservjs exposes scanning through the browser.
OPNsense and WireGuard control network access into the management VLAN.

That is the final operational design.