Portal not appearing, restarting server does not work
Hi! Without errors during the installation, unfortunately I can't get it to run on my Pi 4 with Bookworm. The network appears and I can connect to it, but it shows no website. Running sudo systemctl status access-point-server shows "code=exited, status=203/EXEC."
Restarting the server produces the same error. I've re-installed both the OS and the captive portal so many times now and am completely stuck. Any help would be super appreciated!
PS: I've made zero changes to anything, just cloned as descibed in the installation instructions and startet setup.py.
Hey, thanks for reaching out. I'd need further logs to be able to give advice, please attach the full log. You might also want to use journalctl, e.g. see the usage here.
If you can indeed connect to the access point, but just don't see the website, then it's probably the Node.js server that somehow doesn't spin up, see your code=exited. Note that at this point you might as well just use any other server, it doesn't necessarily have to be Node.js, could be anything you like, even a Python http.server.
To further debug, you might also want to run the server code just like the registered access-point-server service would, i.e.
cd server/
PORT=3000 /usr/bin/npm start
This is probably the easiest way to find out why the server is not starting. You can also try the other npm scripts I've added, see this code, e.g. use npm run build-start when you made some changes to the Typescript code. Note that npm start is just an alias for npm run start.
Thanks for the quick reply! I tried cd server/ PORT=3000 /usr/bin/npm start
When I do this, I get bash: /usr/bin/npm - no such file or directory.
Indeed when I go into usr/bin in the file manager, there is no file called npm. Seems the installer can't/doesn't install it. I ran it again, no errors at all.
I'm really unfamiliar with Linux, so I would not know where to even read detailed log files from...
Could you try out if my code changes (where I explicitly install npm as well) work for you? To do so, check out the branch fix/install-npm of this repo and rerun the setup script:
# assuming you're at the root of this repository
git pull
git switch fix/install-npm
sudo python setup.py
At this point, also just a regular npm --version command on your shell should work fine. As well as /usr/bin/npm --version. If not, try to close your shell and reopen it again.
Hi! the plot thickens. I tried that, and make quadruple sure I'm on the fix/install-npm branch. It installs ok, no errors, but the same issue continue to happen. bash: /usr/bin/npm - no such file or directory. Indeed there still is no npm file in usr/bin.
To be sure I'm not doing anything wrong:
- With a wiped SD card, I start the pi 4 and hold Shift
- There I install the recommended PiOS (Bookworm 64bit) with all default settings.
- When prompted, I set my location (Germany), but check "Use English language".
- When the desktop appears, I go into the Terminal, clone the repo, and start setup.py I do not install any other software, and I have made no changes at all to the default installation of PiOS apart from setting the locale when prompted. Is there anything I'm doing wrong?
Your steps seem fine. Maybe you could just manually follow the steps on the nvm site and report what commands worked for you to install npm?
. $HOME/.nvm/nvm.sh && nvm install 22
From this page I also drew the inspiration to use nvm install-latest-npm.
See also these lines of my setup script as well as the Node.js download guide. Over there, there is likewise no special step for installing npm.
# Download and install nvm:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.2/install.sh | bash
# in lieu of restarting the shell
\. "$HOME/.nvm/nvm.sh"
# Download and install Node.js:
nvm install 22
# Verify the Node.js version:
node -v # Should print "v22.14.0".
nvm current # Should print "v22.14.0".
# Verify npm version:
npm -v # Should print "10.9.2".
It gets worse and worse. I've spent close to 10 hours yesterday, without making any progress :(
All you wrote above works, it shows exactly the versions you write. But trying to start the server, the same error happens.
`PORT=3000 npm start
> [email protected] start
> node build/server.js
node:internal/modules/cjs/loader:1228
throw err;
^
Error: Cannot find module '/home/rochus/raspi-captive-portal/server/build/server.js'
at Function._resolveFilename (node:internal/modules/cjs/loader:1225:15)
at Function._load (node:internal/modules/cjs/loader:1055:27)
at TracingChannel.traceSync (node:diagnostics_channel:322:14)
at wrapModuleLoad (node:internal/modules/cjs/loader:220:24)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:170:5)
at node:internal/main/run_main_module:36:49 {
code: 'MODULE_NOT_FOUND',
requireStack: []
}
Node.js v22.14.0
`
And the status is still the same:
`:~/raspi-captive-portal/server $ sudo systemctl status access-point-server
● access-point-server.service - Raspi Access Point Server Service
Loaded: loaded (/etc/systemd/system/access-point-server.service; enabled; >
Active: activating (auto-restart) (Result: exit-code) since Sat 2025-03-29>
Process: 6536 ExecStart=/usr/bin/npm start (code=exited, status=203/EXEC)
Main PID: 6536 (code=exited, status=203/EXEC)
CPU: 3ms
`
I've probably read every bit of documentation there is, but nothing works, Either I have a broken Pi, or they leave out many steps they take fro granted. For example it seems none of the installers even installs into usr/bin, they all seem to install into home/MYUSER/.nvm
During install, this seems like it's where it breaks:
▶ Install Node.js dependencies for backend
/bin/sh: 1: npm: not found
▶ Build Node.js server (typescript)
This might take some time...
/bin/sh: 1: npm: not found
But
npm --version
10.8.2
so it seems installed, the setup script just thinks it isn't. Could it be that it expects it to be in usr/bin, but it is installed somewhere else?
Thank you @Splines for this excellent project, for which I have a proper use case in mind that I'll be happy to tell you about in the future.
But for now I'm having terrible trouble with node and npm versions, and I'd appreciate any suggestions. This is on a Pi 5 with the latest Bookworm. I'm running over SSH, if that makes any difference.
Originally I had just the same problem as @paleiadevelopment, and got no joy with the fix/install-npm patch. So I installed nvm manually. Rebooting and checking versions, I have node 22.17.1 and npm 10.9.2, and nvm ls says that 22.17.1 is the only version installed. Yet the captive portal doesn’t work, even though the results of sudo systemctl status hostapd and sudo systemctl status access-point-server both look fine.
So I re-run setup.py, which says:
You have Node.js v18.19.0 and npm v9.2.0 installed.
Following the suggestion to upgrade, it says “Now using node v22.17.1 (npm v10.9.3)” and everything works fine… until the next reboot. The system is awesome when it works, and re-running the install script would normally be no big deal. But in this case it will be going into an environment where it will need to look after itself following power cuts and whatnot. Any ideas?
@CharlesButcher Mmh, ok, this seems like an issue with the system vs. the shell context. Note that apparently my script doesn't detect your newer nvm-managed Node version (v22.17.1), but instead your system-wide version v18.19.0 (that you maybe installed via apt install nodejs or through some other means). It works when you rerun the setup.py script after reboot, since then nvm is available in the shell context. When you manually install nvm on your system, you see something like v22.17.1 since your shell profile loads nvm (e.g. via .bashrc or .profile) and therefore has the newest version available.
Can you try experimenting with the following suggestions and report back on it? Checkout the branch fix/install-npm first. Then:
- The Usage section of nvm explains that you can actually use
nvm install nodeinstead ofnvm install <specific-node-version>. This should be more robust since it always installs the latest release of node. So replace the stringnvm install {NODE_JS_VERSION}in thesetup.pyscript bynvm install node. - According to this StackOverflow answer, add
&& nvm alias default node(see also the nvm docs on this) to the line that does thenvm install node.
It should look something like this in the end:
if not installed:
# https://nodejs.org/en/download
subprocess.run(
"curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.2/install.sh | bash",
shell=True,
check=True,
)
subprocess.run(
f". $HOME/.nvm/nvm.sh && nvm install node && nvm install-latest-npm && nvm alias default node",
shell=True,
check=True,
executable="/bin/bash",
)
Also verify that npm version --json gives you the latest versions on your shell. If it still does not work, maybe the Python script really does not load nvm correctly (even though we specify shell=True for subprocess.run()). In that case, maybe manually loading the .bashrc profile works at the top of the Python script? Or it could be that the npm install-latest-npm is not doing what we expect...
One more thing: you said that you re-ran the setup.py script. Apparently, it upgraded your npm version from the previously manually installed version v10.9.2 to a newer version v10.9.3. Was this persistent on the next reboot, or did you see v10.9.2 again?
And one more comment regarding this:
and
nvm lssays that22.17.1is the only version installed.
nvm says about itself that:
nvm is a version manager for node.js, designed to be installed per-user, and invoked per-shell.
So, it will not be installed into /usr/local/bin, but instead to something like ~/.nvm. This is good since it allows to have one specific node version for one specific project without having to manage symlinks and to get confused about system-wide installations.
But therefore I also assume that nvm does not know about the system-wide Node.js version installed through apt install nodejs. But this shouldn't be a problem in the end if we set the nvm alias default node to use the latest Node.js version that you installed.
Thank you @Splines. After a lot of self-inflicted messing around with my manual install of nvm (via https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh) I was getting nowhere.
But I've noticed that after a reboot, running access-point/setup-access-point.sh gets everything working, with no need to run setup.py again. Any clues there?
After moving to fix/install-npm and making your suggested edits to setup.py, there was a point where the script claimed that it already had node 24.5.0 and npm 10.9.3. I thought that was progress, even though it still failed again on rebooting. But now, for no reason I can see, it's reverted to node 18.19.0 and npm 9.2.0, though it still claims to update successfully to node 24.5.0 and npm 10.9.3.
I did also wonder whether we'd successfully created access-point-server.service but that it just wasn't loading at boot time. I see it refers to /usr/bin/npm, which is 9.2.0. But maybe that's what you knew was happening anyway – it's all over my head. Running the individual commands from setup-server.sh throws no errors, but I don't think it fixes the issue. I don't suppose there's anything wrong with these permissions?
$ ls -al /etc/systemd/system/ | grep access
-rw-r--r-- 1 root root 260 Aug 1 01:59 access-point-server.service
Ok, maybe there's some misunderstanding here and I admit that I didn't specify it too clearly in the Readme.
But I've noticed that after a reboot, running
access-point/setup-access-point.shgets everything working, with no need to runsetup.pyagain. Any clues there?
The setup.py script is intended to be only run once on your Raspi. What it does is essentially to copy configuration files to some locations on your Raspi and to register usual Linux services that will automatically start upon reboot (e.g. the service hostapd). This is the main idea of the repo. Instead of having to configure everything manually, which took me just way too long initially, it provides some defaults and also copies files to the right locations. On top of that, it sets up a very basic server (here by default Node.js with Express, but you might want to use any other server you want in any programming language).
You only need to rerun the setup.py script in case you modified some config files, e.g. because you wanted to rename the WiFi network (see Customization section in the Readme).
@CharlesButcher maybe it's possible for you to start from a fresh Raspi Bookworm install? Then describe exactly what is not working for you upon restart and work through the Troubleshooting section. Also post logs of error messages and status infos about services (e.g. systemctl status <servicename> and journalctl -u <servicename>).
Ideally, the setup.py should be idempotent in the sense that when you have to run it again, it detects that in the last run, you already installed the new Node.js version. I admit that this part is probably not working as expected right now. However, if everything else works fine, then you've basically achieved almost all you need. Your setup seems to work in the sense that all WiFi-related services are properly starting after reboot.
The only thing missing is maybe that your local server also starts automatically. This is achieved by the access-point-server.service, which is registered as service (that starts automatically upon system reboot). Every time the service is (re)started, it will run the command /usr/bin/npm start for you (see the line ExecStart=/usr/bin/npm start). Replace this command by whatever you want to get your server up and running. It's probably this /usr/bin/npm that is the problem since maybe npm is installed to another location by nvm, hence the version mismatches. I have to look into this on my own on a fresh install.
Thanks for your patience, and sorry for any misunderstandings as well as my lack of skill. I do see that setup.py is supposed to run only once, but doing that after each reboot previously seemed to be the only way to get the captive portal running again.
I think your previous analysis was correct, and that the server failed to start because npm was not where ExecStart=/usr/bin/npm start expected. Now after a clean install of Debian, moving to the fix/install-npm branch plus the manual edits you suggested, and a great deal of messing around with Node.js versions, I think that must be solved.
Now the only problem seems to be hostapd not starting properly.
After a reboot, the network isn’t visible and I get:
pi@museum:~/raspi-captive-portal $ sudo systemctl status hostapd
● hostapd.service - Access point and authentication server for Wi-Fi and Ethernet
Loaded: loaded (/lib/systemd/system/hostapd.service; enabled; preset: enabled)
Active: active (running) since Fri 2025-08-01 16:05:03 BST; 9min ago
Docs: man:hostapd(8)
Process: 795 ExecStart=/usr/sbin/hostapd -B -P /run/hostapd.pid $DAEMON_OPTS ${DAEMON_CONF} (code=exited, status=0/SUCC>
Main PID: 909 (hostapd)
Tasks: 1 (limit: 9577)
CPU: 9ms
CGroup: /system.slice/hostapd.service
└─909 /usr/sbin/hostapd -B -P /run/hostapd.pid /etc/hostapd/hostapd.conf
Aug 01 16:05:01 museum systemd[1]: Starting hostapd.service - Access point and authentication server for Wi-Fi and Ethernet>
Aug 01 16:05:03 museum hostapd[795]: wlan0: interface state UNINITIALIZED->COUNTRY_UPDATE
Aug 01 16:05:03 museum hostapd[795]: wlan0: interface state COUNTRY_UPDATE->ENABLED
Aug 01 16:05:03 museum hostapd[795]: wlan0: AP-ENABLED
Aug 01 16:05:03 museum systemd[1]: Started hostapd.service - Access point and authentication server for Wi-Fi and Ethernet.
sudo systemctl restart hostapd fixes the issue, and:
pi@museum:~/raspi-captive-portal $ sudo systemctl status hostapd
● hostapd.service - Access point and authentication server for Wi-Fi and Ethernet
Loaded: loaded (/lib/systemd/system/hostapd.service; enabled; preset: enabled)
Active: active (running) since Fri 2025-08-01 16:16:04 BST; 29s ago
Docs: man:hostapd(8)
Process: 2621 ExecStart=/usr/sbin/hostapd -B -P /run/hostapd.pid $DAEMON_OPTS ${DAEMON_CONF} (code=exited, status=0/SUC>
Main PID: 2623 (hostapd)
Tasks: 1 (limit: 9577)
CPU: 6ms
CGroup: /system.slice/hostapd.service
└─2623 /usr/sbin/hostapd -B -P /run/hostapd.pid /etc/hostapd/hostapd.conf
Aug 01 16:16:04 museum systemd[1]: Starting hostapd.service - Access point and authentication server for Wi-Fi and Ethernet>
Aug 01 16:16:04 museum hostapd[2621]: wlan0: interface state UNINITIALIZED->COUNTRY_UPDATE
Aug 01 16:16:04 museum hostapd[2621]: wlan0: interface state COUNTRY_UPDATE->ENABLED
Aug 01 16:16:04 museum hostapd[2621]: wlan0: AP-ENABLED
Aug 01 16:16:04 museum systemd[1]: Started hostapd.service - Access point and authentication server for Wi-Fi and Ethernet.
Aug 01 16:16:14 museum hostapd[2623]: wlan0: STA f2:68:b1:85:3e:62 IEEE 802.11: associated
Aug 01 16:16:14 museum hostapd[2623]: wlan0: STA f2:68:b1:85:3e:62 RADIUS: starting accounting session 1B1D47F834BF7C31
Aug 01 16:16:23 museum hostapd[2623]: wlan0: STA f2:68:b1:85:3e:62 IEEE 802.11: disassociated
So those three lines at the bottom seem to make all the difference. Please tell me it doesn't have to do with Node.js!
Any suggestions for a fix would be greatly appreciated, since this thing is going to have to run without much oversight.
About the previous Node stuff I’m not sure what to say. I definitely had a problem with different locations; setup.py could apparently see and install to /root/.nvm but this wasn’t accessible to all parts of the installation scripts. I ended up consulting this fine resource and choosing the “Install with apt, using NodeSource Binary Distribution” option, alongside my previous manual installation via nvm. It’s a mess for sure but it seems to work now.
I had a similar issue: the Python script thought npm was installed, but npm was not in the system directories. The Python script worked after I did
$ sudo apt install npm