How to Make a Captive Portal of Death by Trevor Phillips


You do it. I do it. Everyone does it without thinking.

You connect your phone to that “Free Public Wi-Fi” at the airport or coffee shop without a second glance, even if your phone warns you that it’s an “unsecure” network. Sometimes you are faced with a captive portal: one of those annoying pop-ups that asks you to sign in or accept the Terms & Conditions before you can connect.

With just a few pieces of equipment, you can make your own “Free Public Wi-Fi” (or whatever you want to name it) with any page you could imagine as your captive portal.

Hopefully, after seeing how easy it is to get this set up, you will be more cautious before entering credentials or personal data into a Wi-Fi network’s captive portal.


Here’s the equipment I am using for this bad boy:

  • USB Hub to increase range of the Wi-Fi dongle (so that it doesn’t rely on the 5V supply directly from the Pi)

Creating a Hotspot

1. hostapd (Host Access Point Daemon)

On your Raspberry Pi, install hostapd and configure /etc/hostapd/hostapd.conf like so:


Enable hostapd every time the Pi boots:

$ sudo systemctl enable hostapd

Then set DAEMON_CONF="/etc/hostapd/hostapd.conf" in the file /etc/default/hostapd.

  • hostapd will start every time the Pi boots
  • It will try to use the interface wlan1. This is the interface your Wi-Fi dongle is using. For me, it's wlan1.
  • Hardware mode g refers to IEEE 802.11g (2.4 GHz).
  • channel=11 is somewhat arbitrary. There are 14 channels in the 2.4 GHz band.
  • macaddr_acl=0 means the Access Control List will accept everything unless it's specifically in the deny list.

2. DHCP Server (Dynamic Host Configuration Protocol)

Install isc‐dhcp‐server and edit the file /etc/dhcp/dhcpd.conf. Uncomment the line:

# autoritative

Now add this to the end of the file:

This means that clients who connect to the network will receive IP addresses between The router (Raspberry Pi) IP address is The first 24 bits of the IP address are part of the network portion of the address, leaving the remaining 8 out of 32 bits for hosts on the hotspot. The IP addresses and are simply Google's DNS servers.

3. Configure network interfaces

Modify the file /etc/network/interfaces for wlan1.

Note that auto INTERFACE means to start the interface on boot. Next, uncomment

in the file /etc/sysctl.conf.

4. Configure iptables

Run the command

$ sudo iptables ‐t nat ‐A POSTROUTING ‐o wlan0 ‐j MASQUERADE

  • -t nat → use the NAT table (Network Address Translation)
  • -A POSTROUTING → (or --append) alter packets as they are about to go out
  • -o wlan0 → (or --out-interface) chained with -A option to specify the interface via which a packet is going to be sent (use the Raspberry Pi's main, normal interface which should have an Internet connection via another network)
  • ‐j MASQUERADE → allows routing traffic without disrupting the original traffic

Then persist iptables simply by running the command

$ sudo apt‐get install iptables‐persistent

If you need to re-save the iptables configuration, do it like this:

5. Save and reboot

$ sudo shutdown -r now

Creating a Captive Portal

1. nodogsplash

For this, we’re going to use the open source project nodogsplash. Its documentation states that it is “a Captive Portal that offers a simple way to provide restricted access to the Internet by showing a splash page to the user before Internet access is granted.”

I used version 3.0, so I can’t guarantee that this will work if you use another version. After cloning the Git repo, run git checkout v3.0.0 to checkout the same version.

Edit the file /etc/nodogsplash/nodogsplash.conf with the contents from this Gist. To understand all of the settings, I recommend reading the documentation. But in brief:

  • It will use interface wlan1, the interface that the Wi-Fi dongle is using.
  • It’s important to allow pre-authenticated users to have access to port 53 in order for their device to resolve DNS (*more on that below).

Why do pre-authenticated users need to have access to port 53? When a computer or mobile device connects to a Wi-Fi network, there is often a built-in mechanism to detect a captive portal and launch a native browser to accept Terms & Conditions. For example, an iPhone launches a request to and checks if the response is “Success.” If it is not“Success” then the phone knows it needs to go through a captive portal. Without port 53, the device is unable to resolve DNS and get a response on whether it has a successful connection or not.

2. FAS

FAS is the meat of the captive portal. Here is where you craft your special website or webpage that the user must go through before receiving Internet access. It can be anything: an image of hairy monkeys eating grapes, a Terms & Conditions page, or something more malicious like a login page which looks identical to that of a popular website. This can be especially devious if you give the Wi-Fi hotspot a name relevant to the popular website you are emulating. And not legal, so don’t do it! And don’t put credentials into a public Wi-Fi captive portal!

Not only can the webpage look like anything, you can also make it with pretty much anything that will run on the Raspberry Pi (which is now acting as a server). That means HTML+CSS+Javascript, Angular, Ruby on Rails, node.js, etc. For this example, I chose Python-Flask.

The Python server is configured to listen on port 8000, because this is the port we specified in the nodogsplash configuration. When a user enters the captive portal, he will be served the index (/) page of the Python-Flask server. nodogsplash will send ?tok=XXX&redir=YYY to the index page, via optional parameters in the URL. After you've done... whatever it is you wanted to do... the user should be redirected to http://<nodogsplashIP:nodogsplashPORT>/nodogsplash_auth/?tok=tok&redir=redir where the values of tok and redir are the same as those passed in the initial URL. After nodogsplash receives this GET request, it understands that the user has been granted Internet access.

nodogsplashIP should be the IP address of the router (the Pi) and if you are using my configuration file, nodogsplashPORT should be 2050.

Tips for doing this on the Raspberry Pi


  • lsusb → show attached USB devices
  • lsmod → show loaded kernel modules and generally useful system information
  • dmesg → show system messages
  • lshw → show hardware information
  • wpa_cli → initiate a command-line tool to setup/scan Wi-Fi networks (especially useful if you are connecting to the Pi via SSH)

Random stuff

You can add commands in /etc/rc.local to run them at boot. Be warned that if one command fails, the script will exit, so the subsequent commands will not execute.

Place an empty file named ssh in the boot partition of your SD card, to auto-enable SSH (note that it is important to have dtoverlay=dwc2 in the config.txt file).

You can also make a file named wpa_supplicant.conf in the boot partition of your SD card to automatically connect to a network. An example is below:

Originally posted at:

January 10, 2019


Hakin9 TEAM
Hakin9 is a monthly magazine dedicated to hacking and cybersecurity. In every edition, we try to focus on different approaches to show various techniques - defensive and offensive. This knowledge will help you understand how most popular attacks are performed and how to protect your data from them. Our tutorials, case studies and online courses will prepare you for the upcoming, potential threats in the cyber security world. We collaborate with many individuals and universities and public institutions, but also with companies such as Xento Systems, CATO Networks, EY, CIPHER Intelligence LAB, redBorder, TSG, and others.
Notify of

This site uses Akismet to reduce spam. Learn how your comment data is processed.

1 Comment
Oldest Most Voted
Inline Feedbacks
View all comments
© HAKIN9 MEDIA SP. Z O.O. SP. K. 2023