Skip to main content

WiFi Setup

This guide will help you set up a WiFi Access Point on your Raspberry Pi while keeping it connected to your home WiFi network. The Pi will use a single WiFi chip to run both connections simultaneously - wlan0 for your home WiFi and a virtual ap0 interface for the AP.

The Access Point is used for device presence detection (phones connecting to it trigger smart recording).

note

Both the Access Point and the home WiFi connection must operate on the same channel since the Pi only has one WiFi radio.

Automated setup

The easiest way to set up the AP is with the provided script:

cd /opt/security-cam
sudo ./setup/setup_ap.sh

The script will prompt you for:

  • SSID - the AP network name (default: SecurityCam)
  • Password - minimum 8 characters
  • Channel - auto-detected from your current WiFi connection

You can also run it non-interactively:

sudo ./setup/setup_ap.sh --ssid SecurityCam --password YourPasswordHere --channel 6

If the script completes successfully, skip ahead to Verify the setup.

Manual setup

If you prefer to set things up manually, follow the steps below.

Find your WiFi channel

iw dev

You should see an output similar to this:

Example Output:
pi@securitycam:~ $ iw dev
phy#0
Unnamed/non-netdev interface
wdev 0x2
addr 2e:cf:xx:xx:xx:xx
type P2P-device
txpower 31.00 dBm
Interface wlan0
ifindex 2
wdev 0x1
addr 2c:cf:xx:xx:xx:xx
ssid FRITZ!Box 6690
type managed
channel 6 (2437 MHz), width: 20 MHz, center1: 2437 MHz
txpower 31.00 dBm

Note the channel number (channel 6 in this case) - you will need it later.

Create the virtual AP interface

The Pi's WiFi chip supports running a second virtual interface for the Access Point. We create a systemd service so this interface is automatically set up on every boot.

  1. Copy the service file:

    sudo cp /opt/security-cam/client/data/create-ap0.service /etc/systemd/system/
  2. Enable and start it:

    sudo systemctl daemon-reload
    sudo systemctl enable create-ap0.service
    sudo systemctl start create-ap0.service
  3. Verify the ap0 interface exists:

    iw dev | grep ap0

Tell NetworkManager to ignore ap0

NetworkManager must not manage ap0 - hostapd will handle it directly.

sudo mkdir -p /etc/NetworkManager/conf.d
sudo tee /etc/NetworkManager/conf.d/ignore-ap0.conf > /dev/null << 'EOF'
[keyfile]
unmanaged-devices=interface-name:ap0
EOF
sudo systemctl reload NetworkManager

Install hostapd and dnsmasq

sudo apt install hostapd dnsmasq -y

Configure hostapd

Create the hostapd configuration. Replace the values as needed:

  • SecurityCam - your desired network name (SSID)
  • YourPasswordHere - your desired WiFi password (min 8 characters)
  • channel=6 - the channel from the first step
sudo tee /etc/hostapd/hostapd.conf > /dev/null << 'EOF'
interface=ap0
driver=nl80211
ssid=SecurityCam
hw_mode=g
channel=6
wmm_enabled=0
auth_algs=1
wpa=2
wpa_passphrase=YourPasswordHere
wpa_key_mgmt=WPA-PSK
rsn_pairwise=CCMP
EOF

Point the hostapd daemon to this config:

sudo tee /etc/default/hostapd > /dev/null << 'EOF'
DAEMON_CONF="/etc/hostapd/hostapd.conf"
DAEMON_OPTS=""
EOF

Ensure correct service ordering

hostapd must start after ap0 is created, and dnsmasq after hostapd:

sudo mkdir -p /etc/systemd/system/hostapd.service.d
sudo tee /etc/systemd/system/hostapd.service.d/after-ap0.conf > /dev/null << 'EOF'
[Unit]
After=create-ap0.service
Requires=create-ap0.service
EOF

sudo mkdir -p /etc/systemd/system/dnsmasq.service.d
sudo tee /etc/systemd/system/dnsmasq.service.d/after-hostapd.conf > /dev/null << 'EOF'
[Unit]
After=hostapd.service
Requires=hostapd.service
EOF

sudo systemctl daemon-reload

Configure dnsmasq

Set up DHCP for devices connecting to the AP:

sudo cp /opt/security-cam/client/data/dnsmasq-ap0.conf /etc/dnsmasq.d/ap0.conf

Set a static IP on ap0

sudo mkdir -p /etc/systemd/network
sudo tee /etc/systemd/network/10-ap0.network > /dev/null << 'EOF'
[Match]
Name=ap0

[Network]
Address=192.168.4.1/24
EOF
sudo systemctl enable systemd-networkd
sudo systemctl restart systemd-networkd

Start the services

sudo ip addr add 192.168.4.1/24 dev ap0 2>/dev/null || true
sudo ip link set ap0 up
sudo systemctl unmask hostapd
sudo systemctl enable hostapd
sudo systemctl enable dnsmasq
sudo systemctl start hostapd
sudo systemctl restart dnsmasq

Internet passthrough

By default, the AP setup enables internet passthrough - devices connected to the Pi's AP share the Pi's WiFi connection (wlan0 → ap0). This is done via IP forwarding and iptables NAT rules.

If you ran setup_ap.sh, this is already configured. To set it up manually:

# Enable IP forwarding
sudo sysctl -w net.ipv4.ip_forward=1
echo "net.ipv4.ip_forward=1" | sudo tee -a /etc/sysctl.conf

# NAT and forwarding rules
sudo iptables -t nat -A POSTROUTING -o wlan0 -j MASQUERADE
sudo iptables -A FORWARD -i ap0 -o wlan0 -j ACCEPT
sudo iptables -A FORWARD -i wlan0 -o ap0 -m state --state RELATED,ESTABLISHED -j ACCEPT

# Persist rules across reboots
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y iptables-persistent
sudo netfilter-persistent save
note

If the Pi has no WiFi connection (e.g. at a remote location), devices can still connect to the AP to access the dashboard - they just won't have internet.

Captive portal and dashboard hostname

The AP is configured with a captive portal and a custom hostname so users can easily access the dashboard.

How it works

  • Custom hostname: http://dashboard.cam resolves to the Pi when connected to the AP
  • Captive portal: When a device connects to the AP, the OS automatically opens a browser showing the dashboard (like airport/hotel WiFi login pages)

The captive portal works by intercepting OS-specific connectivity check domains (e.g. captive.apple.com for iOS, connectivitycheck.gstatic.com for Android) via dnsmasq, and redirecting HTTP traffic on port 80 to the dashboard on port 3000.

caution

Because the captive portal intercepts connectivity checks, devices will show a "No Internet" indicator even when internet passthrough is working. This is cosmetic - internet access works normally for all other traffic.

Configuration

The captive portal is configured in two places:

dnsmasq (/etc/dnsmasq.d/ap0.conf or client/data/dnsmasq-ap0.conf):

# Custom dashboard hostname
address=/dashboard.cam/192.168.4.1

# Captive portal detection domains
address=/captive.apple.com/192.168.4.1
address=/connectivitycheck.gstatic.com/192.168.4.1
address=/clients3.google.com/192.168.4.1
address=/www.msftconnecttest.com/192.168.4.1
address=/nmcheck.gnome.org/192.168.4.1

iptables (redirect port 80 → dashboard port 3000):

sudo iptables -t nat -A PREROUTING -i ap0 -p tcp --dport 80 -j REDIRECT --to-port 3000

If you ran setup_ap.sh, both are already configured.

Enabling / disabling

The captive portal can be toggled from the dashboard under Settings > Appearance > Captive portal. When disabled, the iptables redirect rule is removed so port 80 requests no longer reach the dashboard and devices stop showing the popup.

To disable manually instead, remove the address= lines from the dnsmasq config and the iptables PREROUTING rule, then restart dnsmasq.

Verify the setup

Check that hostapd is running:

sudo systemctl status hostapd

Check that both interfaces are up:

ip addr show wlan0 | head -3
ip addr show ap0 | head -3

You should see wlan0 connected to your home WiFi and ap0 with IP 192.168.4.1.

Try connecting a device (e.g. your phone) to the SecurityCam network:

  • A captive portal browser should automatically open showing the dashboard
  • You can always reach the dashboard at http://dashboard.cam or http://192.168.4.1:3000
  • SSH into the Pi: ssh pi@192.168.4.1

Reboot and confirm persistence

sudo reboot

After reconnecting via SSH, verify everything survived the reboot:

sudo systemctl status hostapd --no-pager
sudo systemctl status dnsmasq --no-pager
ip addr show ap0

Troubleshooting

hostapd fails to start

Check the logs:

journalctl -u hostapd --no-pager -l | tail -20

Common causes:

  • Channel mismatch - ap0 must use the same channel as wlan0. Check with iw dev and update /etc/hostapd/hostapd.conf.
  • ap0 doesn't exist - check create-ap0.service: sudo systemctl status create-ap0.service
  • Interface held by wpa_supplicant - if NetworkManager previously tried to manage ap0 as an AP, wpa_supplicant may still be attached. Recreate the interface:
    sudo iw dev ap0 del
    sudo iw dev wlan0 interface add ap0 type __ap
    sudo ip addr add 192.168.4.1/24 dev ap0
    sudo ip link set ap0 up
    sudo systemctl restart hostapd
  • Interface managed by NM - nmcli device status should show ap0 as unmanaged. If not, check /etc/NetworkManager/conf.d/ignore-ap0.conf exists and reload NM.

No IP address on connected devices

Check dnsmasq:

sudo systemctl status dnsmasq --no-pager
journalctl -u dnsmasq --no-pager -l | tail -20

Make sure ap0 has its static IP: ip addr show ap0

Checking connected devices

List devices currently connected to the AP:

sudo hostapd_cli all_sta

View DHCP leases:

cat /var/lib/misc/dnsmasq.leases
note

These instructions were tested on Raspberry Pi OS Trixie with a Raspberry Pi Zero 2 W.

caution

Running simultaneous AP and client on a single WiFi chip halves the available bandwidth since the radio time-shares between both roles. If you experience connection drops or need more bandwidth, consider using a USB WiFi dongle for one of the connections.

Continue to bluetooth.mdx to set up Bluetooth.