WiFi Captive Portal

A captive portal is a piece of software that prompts for user interaction before allowing the client to access the internet or other resources on the network. It is a combination of a firewall and a webserver. In this tutorial, I will explain how to create an open WiFi network. Before deploying an open WiFi network, you may want to consult a lawyer of the legality and restrictions for having one. You can also review what has been said by lawyers here and here.

When a device first connects to any network, it sends out a HTTP request and expects an HTTP status code of 200. If the device receives a HTTP 200 status code, it assumes it has unlimited internet access. Captive portal prompts are displayed when you are able to manipulate this first HTTP message to return a HTTP status code of 302 (redirect) to the captive portal of your choice.

To set up a captive portal on a Raspberry Pi, you will need a wired network (I will refer to this as WAN or uplink) and a wireless network (such as the Ralink RT5372 or the Ralink RT5370) which you can set into AP mode. A server with 2 NICs would also suffice if you want to perform this on a wired LAN instead.

 

UPDATE 2/15/2017: If you get the too many redirects error, look at the hotspot.localnet nginx configuration. It could be that the dollar signs $ are not present. The below block is what the location / block should look like in the hotspot.localnet virtual host.

     location / {
         try_files $uri $uri/ index.php;
     }

Operating System

The operating system assumed will be Rapsbian Jessie. You can also follow this tutorial with a Debian server with two NICs – as long as one is dedicated to the WAN and the other to the LAN. It is also possible to use another Linux Distribution, but converting the commands will be up to you. In a later article, I will describe how to perform the steps on OpenWRT.

Required Packages

sudo apt-get install iptables-persistent conntrack dnsmasq nginx php5 php5-common php5-fpm hostapd

Configuring dnsmasq

You can certainly use other DNS and DHCP servers, but I like to use dnsmasq as it is lightweight and found on many routers already. This indicates to me that dnsmasq is pretty stable and able to perform in the enterprise.

The contents of my dnsmasq.conf are as follows:

# Never forward addresses in the non-routed address spaces.
bogus-priv

# Add other name servers here, with domain specs if they are for
# non-public domains.
server=/localnet/192.168.24.1

# Add local-only domains here, queries in these domains are answered
# from /etc/hosts or DHCP only.
local=/localnet/

# If you want dnsmasq to listen for DHCP and DNS requests only on
# specified interfaces (and the loopback) give the name of the
# interface (eg eth0) here.
# Repeat the line for more than one interface.
interface=wlan0

# Set the domain for dnsmasq. this is optional, but if it is set, it
# does the following things.
# 1) Allows DHCP hosts to have fully qualified domain names, as long
#     as the domain part matches this setting.
# 2) Sets the "domain" DHCP option thereby potentially setting the
#    domain of all systems configured by DHCP
# 3) Provides the domain part for "expand-hosts"
domain=localnet

# Uncomment this to enable the integrated DHCP server, you need
# to supply the range of addresses available for lease and optionally
# a lease time. If you have more than one network, you will need to
# repeat this for each network on which you want to supply DHCP
# service.
dhcp-range=192.168.24.50,192.168.24.250,2h

# Override the default route supplied by dnsmasq, which assumes the
# router is the same machine as the one running dnsmasq.
dhcp-option=3,192.168.24.1

#DNS Server
dhcp-option=6,192.168.24.1

# Set the DHCP server to authoritative mode. In this mode it will barge in
# and take over the lease for any client which broadcasts on the network,
# whether it has a record of the lease or not. This avoids long timeouts
# when a machine wakes up on a new network. DO NOT enable this if there's
# the slightest chance that you might end up accidentally configuring a DHCP
# server for your campus/company accidentally. The ISC server uses
# the same option, and this URL provides more information:
# http://www.isc.org/files/auth.html
dhcp-authoritative

dnsmasq also reads in the /etc/hosts file and adds that information into the dns. Since we want to use pretty hostnames in our captive portal urls, we need to add a few entries to our hosts file.

# Contents of /etc/hosts
127.0.0.1	localhost 
192.168.24.1	hotspot.localnet
::1		localhost ip6-localhost ip6-loopback
fe00::0		ip6-localnet
ff00::0		ip6-mcastprefix
ff02::1		ip6-allnodes
ff02::2		ip6-allrouters

Network

Now that we have DNS and DHCP set up, we need to set up the interfaces. Edit /etc/network/interfaces and place in the following information:

# The loopback network interface
auto lo eth0
iface lo inet loopback

# The "wan" network interface
iface eth0 inet dhcp

# The "lan" network interface
iface wlan0 inet static
  address 192.168.24.1
  netmask 255.255.255.0

We also need to tell the kernel to forward IP packets between interfaces. To do that, enter the following command

echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf

You will need to reboot in order for that change to take effect, but do not do that quite yet.

Hostapd

hostapd will be the service that sets up the SSID and ensures it is broadcasting over the wireless card. If you are setting up the captive portal on a server with 2 NICs you can skip to the next section.

While you may certainly set up hostapd in WPA2 mode, I am assuming that because we are setting up a captive portal, this will be our authentication method rather than the encryption setting on the access point. To set up an open WiFi network, edit /etc/hostapd/hostapd.conf with the following information

interface=wlan0
ssid=MyOpenAP # name of the WiFi access point
hw_mode=g
channel=6 #use 1, 6, or 11
auth_algs=1
wmm_enabled=0

You will now have to set up the daemon by editing the contents of /etc/default/hostapd and change the line:

#DAEMON_CONF=""

to:

DAEMON_CONF="/etc/hostapd/hostapd.conf"

Iptables

iptables is our firewall. This is how we are going to block all connections that we have not authorized. Below are the commands to set up the firewall:

# Turn into root
sudo -i
# Flush all connections in the firewall
iptables -F
# Delete all chains in iptables
iptables -X
# wlan0 is our wireless card. Replace with your second NIC if doing it from a server.
# This will set up our structure
iptables -t mangle -N wlan0_Trusted
iptables -t mangle -N wlan0_Outgoing
iptables -t mangle -N wlan0_Incoming
iptables -t mangle -I PREROUTING 1 -i wlan0 -j wlan0_Outgoing
iptables -t mangle -I PREROUTING 1 -i wlan0 -j wlan0_Trusted
iptables -t mangle -I POSTROUTING 1 -o wlan0 -j wlan0_Incoming
iptables -t nat -N wlan0_Outgoing
iptables -t nat -N wlan0_Router
iptables -t nat -N wlan0_Internet
iptables -t nat -N wlan0_Global
iptables -t nat -N wlan0_Unknown
iptables -t nat -N wlan0_AuthServers
iptables -t nat -N wlan0_temp
iptables -t nat -A PREROUTING -i wlan0 -j wlan0_Outgoing
iptables -t nat -A wlan0_Outgoing -d 192.168.24.1 -j wlan0_Router
iptables -t nat -A wlan0_Router -j ACCEPT
iptables -t nat -A wlan0_Outgoing -j wlan0_Internet
iptables -t nat -A wlan0_Internet -m mark --mark 0x2 -j ACCEPT
iptables -t nat -A wlan0_Internet -j wlan0_Unknown
iptables -t nat -A wlan0_Unknown -j wlan0_AuthServers
iptables -t nat -A wlan0_Unknown -j wlan0_Global
iptables -t nat -A wlan0_Unknown -j wlan0_temp
# forward new requests to this destination
iptables -t nat -A wlan0_Unknown -p tcp --dport 80 -j DNAT --to-destination 192.168.24.1
iptables -t filter -N wlan0_Internet
iptables -t filter -N wlan0_AuthServers
iptables -t filter -N wlan0_Global
iptables -t filter -N wlan0_temp
iptables -t filter -N wlan0_Known
iptables -t filter -N wlan0_Unknown
iptables -t filter -I FORWARD -i wlan0 -j wlan0_Internet
iptables -t filter -A wlan0_Internet -m state --state INVALID -j DROP
iptables -t filter -A wlan0_Internet -o eth0 -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
iptables -t filter -A wlan0_Internet -j wlan0_AuthServers
iptables -t filter -A wlan0_AuthServers -d 192.168.24.1 -j ACCEPT
iptables -t filter -A wlan0_Internet -j wlan0_Global
# allow access to my website :)
iptables -t filter -A wlan0_Global -d andrewwippler.com -j ACCEPT
#allow unrestricted access to packets marked with 0x2
iptables -t filter -A wlan0_Internet -m mark --mark 0x2 -j wlan0_Known
iptables -t filter -A wlan0_Known -d 0.0.0.0/0 -j ACCEPT
iptables -t filter -A wlan0_Internet -j wlan0_Unknown
# allow access to DNS and DHCP
# This helps power users who have set their own DNS servers
iptables -t filter -A wlan0_Unknown -d 0.0.0.0/0 -p udp --dport 53 -j ACCEPT
iptables -t filter -A wlan0_Unknown -d 0.0.0.0/0 -p tcp --dport 53 -j ACCEPT
iptables -t filter -A wlan0_Unknown -d 0.0.0.0/0 -p udp --dport 67 -j ACCEPT
iptables -t filter -A wlan0_Unknown -d 0.0.0.0/0 -p tcp --dport 67 -j ACCEPT
iptables -t filter -A wlan0_Unknown -j REJECT --reject-with icmp-port-unreachable
#allow forwarding of requests from anywhere to eth0/WAN
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE

#save our iptables
iptables-save > /etc/iptables/rules.v4

nginx

Now we need to set up nginx to send a magic packet that prompts the user for action (i.e MyOpenAP requires you to sign in). To do that, we will run the following commands:

# Make the HTML Document Root
mkdir /usr/share/nginx/html/portal
chown nginx:www-data /usr/share/nginx/html/portal
chmod 755 /usr/share/nginx/html/portal

# create the nginx hotspot.conf file
cat << EOF > /etc/nginx/sites-available/hotspot.conf
server {
    # Listening on IP Address.
    # This is the website iptables redirects to
    listen       80 default_server;
    root         /usr/share/nginx/html/portal;

    # For iOS
    if ($http_user_agent ~* (CaptiveNetworkSupport) ) {
        return 302 http://hotspot.localnet/hotspot.html;
    }

    # For others
    location / {
        return 302 http://hotspot.localnet/;
    }
 }

 upstream php {
    #this should match value of "listen" directive in php-fpm pool
		server unix:/tmp/php-fpm.sock;
		server 127.0.0.1:9000;
	}

server {
     listen       80;
     server_name  hotspot.localnet;
     root         /usr/share/nginx/html/portal;

     location / {
         try_files $uri $uri/ index.php;
     }

    # Pass all .php files onto a php-fpm/php-fcgi server.
    location ~ [^/]\.php(/|$) {
    	fastcgi_split_path_info ^(.+?\.php)(/.*)$;
    	if (!-f $document_root$fastcgi_script_name) {
    		return 404;
    	}
    	# This is a robust solution for path info security issue and works with "cgi.fix_pathinfo = 1" in /etc/php.ini (default)
    	include fastcgi_params;
    	fastcgi_index index.php;
    	fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    	fastcgi_pass php;
    }
}

EOF

# Enable the website and reload nginx
ln -s /etc/nginx/sites-available/hotspot.conf /etc/nginx/sites-enabled/hotspot.conf
systemctl reload nginx

We have just set up the path /usr/share/nginx/html/portal to serve web pages if accessed via IP (note the default_server directive) as well as if accessed by the hostname hotspot.localnet. If accessed via IP, it will trigger a 302 redirect to the hostname hotspot.localnet.

HTML / PHP

When it comes to the coding aspect, we only want to allow access if the webserver has determined the user has clicked the I agree to the terms button. To do that, I will add a <input type="hidden" name="security_code" value="andrew-wippler-is-cool" /> to my form field. This will stop a few script kiddies from gaining access without clicking the button, but one may want to add additional security features if deploying this in the enterprise (or just buy an appliance that has a captive portal already).

We will need two files added to /usr/share/nginx/html/portal. They are the following:

index.php
<!DOCTYPE html>
<?php

// Grant access if the Security code is accurate.
if ($_POST['security_code'] == "andrew-wippler-is-cool") {

// Grab the MAC address
$arp = "/usr/sbin/arp"; // Attempt to get the client's mac address
$mac = shell_exec("$arp -a ".$_SERVER['REMOTE_ADDR']);
preg_match('/..:..:..:..:..:../',$mac , $matches);
$mac2 = $matches[0];

// Reconnect the device to the firewall
exec("sudo rmtrack " . $_SERVER['REMOTE_ADDR']);
$i = "sudo iptables -t mangle -A wlan0_Outgoing  -m mac --mac-source ".$_GET['mac']." -j MARK --set-mark 2";
exec($i);

sleep(5);

?> <html>
<head>
<title></title>
</head>
<body>
<h1>You are now free to browse the internet.</h1>
</body> </html>
<?php } else {
  // this is what is seen when first viewing the page
  ?>
  <html>
  <head>
  <title></title>
  </head>
  <body>
  <h1>Authorization Required</h1>
  <p>Before continuing, you must first agree to the <a href="#">Terms of Service</a> and be of the legal age to do that in your selective country or have Parental Consent.
  </p>
  <form method="post" action="index.php">
    <input type="hidden" name="security_code" value="andrew-wippler-is-cool" />
    <input type="checkbox" name="checkbox1" CHECKED /><label for="checkbox1">I Agree to the terms</label><br />
    <input type="submit" value="Connect" />
  </form>
  </body> </html>
<?php } ?>
hotspot.html
 <!--
 <?xml version="1.0" encoding="UTF-8"?>
 <WISPAccessGatewayParam xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.wballiance.net/wispr_2_0.xsd">
 <Redirect>
 <MessageType>100</MessageType>
 <ResponseCode>0</ResponseCode>
 <VersionHigh>2.0</VersionHigh>
 <VersionLow>1.0</VersionLow>
 <AccessProcedure>1.0</AccessProcedure>
 <AccessLocation>Andrew Wippler is awesome</AccessLocation>
 <LocationName>MyOpenAP</LocationName>
 <LoginURL>http://hotspot.localnet/</LoginURL>
 </Redirect>
 </WISPAccessGatewayParam>
 -->

Additional Configurations

We are almost done; however, you may have noticed the line 14 in index.php which includes executing rmtrack. What is rmtrack? It is the code of Andy Beverley which is licensed under the GNU Free Documentation License 1.3. In Andy’s tutorial for a captive portal, rmtrack is used to remove the connection information in the kernel to prevent login loops. Let’s create that file now:

cat << EOF > /usr/bin/rmtrack
/usr/sbin/conntrack -L \
    |grep $1 \
    |grep ESTAB \
    |grep 'dport=80' \
    |awk \
        "{ system(\"conntrack -D --orig-src $1 --orig-dst \" \
            substr(\$6,5) \" -p tcp --orig-port-src \" substr(\$7,7) \" \
            --orig-port-dst 80\"); }"
EOF

chmod +x /usr/bin/rmtrack

We also have to allow our web server user www-data access to run privileged commands. We can do that by typing visudo and appending the below lines to the file.

www-data ALL = NOPASSWD: /usr/bin/rmtrack [0-9]*.[0-9]*.[0-9]*.[0-9]*
www-data ALL = NOPASSWD: /sbin/iptables -t mangle -A wlan0_Outgoing  -m mac --mac-source ??\:??\:??\:??\:??\:?? -j MARK --set-mark 2

With the sudoers file saved, you are now ready to reboot the Raspberry Pi and enjoy offering a fairly simplistic captive portal.


    ThorstenS

    Really nice setup – I must try it on my pi – thanks for writing it up!
    One improvement:
    Instead of piping through grep like this:
    [/code]/usr/sbin/conntrack -L \
    |grep $1 \
    |grep ESTAB \
    |grep ‘dport=80’ \
    |awk \
    [/code]
    use just awk:
    [code]/usr/sbin/conntrack -L| awk -v IP=$1 ‘/IP/ && /ESTABLI/ && /dport=80/ {…}'[/code]




      awippler

      Thanks. I updated the article with the changes.



    Seagal

    Hello
    I wonder if you can do this using 2 ethernet cards and acces point
    I would add the second ethernet card via USB and card that would connect the access point
    Why I want to do this?
    an access point has greater coverage .
    sorry english is not my native languaje, i used google traslate.



      awippler

      Yes, you can do it with any combination of interfaces. You would need to bond all interfaces in the LAN portion.



    sime

    Hello, how allow HTTPS website in iptables ?

    because facebook.com don’t work ?

    Thanks for advance



    Paul Montague

    Hi Andrew, this is just what I was looking for and have run through the setup on a Pi 3. Get a challenge on devices connecting to the WiFi but with a “403 Forbidden” error.
    Have traced this through nginx var logs and also by doing an strace on the nginx processes.
    Var Log: ….directory index of “/usr/share/nginx/html/portal/” is forbidden, client: 192.168.24.1, server: hotspot.localnet, request: “GET / HTTP/1.1”, host: “hotspot.localnet”
    Strace: [pid 918] stat64(“/usr/share/nginx/html/portal/index.html”, 0x7efa97f0) = -1 ENOENT (No such file or directory)
    Have tried opening up permissions on the portal directory but looks like an issue with nginx or php sourcing a non-existent file?

    Would appreciate any help.



      awippler

      It looks like either there is no index.html file created or the permissions on index.html need to be redone.



    Paul Montague

    Thanks for your response. I only have two files in that directory, hotspot.html and index.php. Should I rename one of them?



    Paul Montague

    Brilliant!
    I renamed index.php to index.html and now get the “Authorization Required” screen on connection to the WiFi.
    What would the index.php look like that is being called by the index.html?



      Frank

      Same here… what about index.php? Thanks.



    Andreas Wittmann

    Hello Andrew, nice project. May you can help me, i got an error “too many redirects”? Would appreciate any help.



      awippler

      It seems you have a redirect loop going on. It could be that by clearing your browser’s cache would fix it. It could also be an issue with how the hostnames and redirects you have set up.

      I had similar issues when setting it up initially. The browser cache was the most common resolution, but I also had misconfigurations.

      This guide was written without any misconfigurations. The only items that need to be changed are the dns entries if you are deploying it to your home network.



    Levi Pihema-Lindsay

    So how would we intercept the first request and forward them to the gateway so as to get them to auth?



      awippler

      When a device first connects to a new wireless access point, it sends out a HTTP request to its favorite URL. Following this guide will alter that first URL/request and give a redirect to the captive portal. You might want to run a packet capture on a laptop when you first connect to the RPi. You will see what I mean.



    knight

    hi will this work with 2 wireless card ?



      awippler

      I have not tried it with 2 wireless NICs. I suppose it could work.



    Levi Pihema-Lindsay

    Hm. the redirect doesn’t seem to be working. Any ideas? everything just times out



      knight

      it won’t work on Https websites. try going to a non-https websites.



        Levi Pihema-Lindsay

        Yes, I know this. However, even on sites that don’t have SSL, I still timeout



    knight

    hi, is there a way to redirect or force open the fake_portal once user connects to the rogue_AP? Something like how mc’donalds or hotel wifi works .. once you connect to their wifi it redirects and opens up their portal …



    M. M.

    Very well written and described.

    Is it also possible to just make a splash page that opens when the user opens browser for the first time and\or every time?



      awippler

      Like no internet access?… Just remove the button on the portal. The traffic flow has to stop for the captive portal to trigger. The button on the captive portal in my tutorial tags the traffic in the firewall to bypass the stopping point.



        M. M.

        Thanks for your response.
        I know it’s a question off the topic because what I mean is a splash page and not a Cuptive portal.
        I actually want the user to have internet access but see a splash page the first time they open browser or maybe every time.



          awippler

          Unfortunately that is not possible. For a splash page to appear, internet traffic flow has to stop.



        knight

        hi, is there a way to redirect or force open the fake_portal once user connects to the rogue_AP? Something like how mc’donalds or hotel wifi works .. once you connect to their wifi it redirects and opens up their portal …



    William

    Hello Andrew, thank you for this great tutorial! I am completely new to this captive portal. I hope you could answer my few questions. 🙂 Very much appreciate in advance!

    1. Do I connect my Pi to the router or modem? If it is connected to the router, so it should be wired, correct? Then the USB WIFI adapter will act as the access point on the Pi? I have Edimax WIFI USB Adapter but it does not give out AP as “MyOpenAP” after configured.

    2. I have this error “Job for nginx.service failed. See ‘systemctl status nginx.service’ and ‘journalctl -xn’ for details.” after entering this line “systemctl reload nginx” How can I fix this?

    3. After this line “chown nginx:www-data /usr/share/nginx/html/portal”, I got an error saying invalid user. Should it just be “chown www-data /usr/share/nginx/html/portal”?

    4. Can I just copy all your config settings and paste to my files? or do I have to modify 192.168.24.1 to my router’s gateway interface (192.168.2.1)?

    THANK YOU AGAIN FOR READING AND HELPING!!!



      William

      btw, I ran the code “journalctl -xn”, this was the log file that came up:
      “– Logs begin at Fri 2016-12-02 02:05:01 CST, end at Fri 2016-12-02 02:30:13 CST. —
      Dec 02 02:30:05 raspberrypi sshd[8021]: PAM 2 more authentication failures; logname= uid=0 euid=0 tty=ssh ruser= rhost=116.31.116.41 us
      Dec 02 02:30:06 raspberrypi sshd[8019]: Received disconnect from 116.31.116.41: 11: [preauth]
      Dec 02 02:30:06 raspberrypi sshd[8019]: PAM 2 more authentication failures; logname= uid=0 euid=0 tty=ssh ruser= rhost=116.31.116.41 us
      Dec 02 02:30:06 raspberrypi sshd[8023]: Failed password for root from 116.31.116.41 port 11105 ssh2
      Dec 02 02:30:06 raspberrypi sshd[8024]: Failed password for root from 116.31.116.41 port 12941 ssh2
      Dec 02 02:30:06 raspberrypi sshd[8023]: Received disconnect from 116.31.116.41: 11: [preauth]
      Dec 02 02:30:06 raspberrypi sshd[8023]: PAM 2 more authentication failures; logname= uid=0 euid=0 tty=ssh ruser= rhost=116.31.116.41 us
      Dec 02 02:30:06 raspberrypi sshd[8024]: Received disconnect from 116.31.116.41: 11: [preauth]
      Dec 02 02:30:06 raspberrypi sshd[8024]: PAM 2 more authentication failures; logname= uid=0 euid=0 tty=ssh ruser= rhost=116.31.116.41 us
      Dec 02 02:30:13 raspberrypi sshd[8092]: fatal: Unable to negotiate a key exchange method [preauth]”

      And after this code “systemctl status nginx”. here is the response:
      “● nginx.service – A high performance web server and a reverse proxy server
      Loaded: loaded (/lib/systemd/system/nginx.service; enabled)
      Active: active (running) since Fri 2016-12-02 02:05:15 CST; 23min ago
      Process: 692 ExecStart=/usr/sbin/nginx -g daemon on; master_process on; (code=exited, status=0/SUCCESS)
      Process: 544 ExecStartPre=/usr/sbin/nginx -t -q -g daemon on; master_process on; (code=exited, status=0/SUCCESS)
      Main PID: 701 (nginx)
      CGroup: /system.slice/nginx.service
      ├─701 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
      ├─702 nginx: worker process
      ├─703 nginx: worker process
      ├─704 nginx: worker process
      └─705 nginx: worker process”



        Zach

        For #2 Check what’s wrong loading nginx config files with:
        $ sudo nginx -t

        I had similar problem



    Ben

    Hello Andrew, thanks for this tutorial. Just a minor problem, it seems that allowing any websites on the iptables rules doesn’t work. Can you help me with this one?



    ESharp

    Cool project! You mentioned that an appliance might be an alternative. Have you seen any that you would recommend?



      awippler

      Ubiquiti offers a hotspot redirection with their UniFi software.



    Hugo Freire

    Hi Andrew thank you for this great tutorial and Happy New Year for you, with that configurations in the nginx server you can have a captive portal popup in the ios 9/10?
    I cant have it in any device and i google it very hard and i cant understand why, im gratefoul if you have any tip for me 🙂

    Sry for my bad english,
    Thanks and Best Regards



    Andre Metelo

    Just to let folks know, you can have https routed to the server through 3 steps:
    Step 1:
    ======
    Add a rule to forward port 443 to the https port int he nginx server
    – iptables -t nat -A wlan0_Unknown -p tcp –dport 443 -j DNAT –to-destination 192.168.24.1
    Remember to save the iptables again

    Step 2:
    – Create a SSL certificate for the NGINX

    sudo mkdir /etc/nginx/ssl
    sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/nginx/ssl/nginx.key -out /etc/nginx/ssl/nginx.crt

    Step 3:
    ======
    Add the listen 443 SSL;
    poiint the cetificates to the files created on page 2.

    That will be enough to route any https resquests to the https port, which then will redirect to http://hotspot.localnet/

    That’s it.

    PS – Thanks for putting this guide together. It was extremely useful for me to put one of those together.



      Andreas

      But won’t the users browser’s complain when they enter https websites like twitter?



    David

    Hi. I have a problem, my captive portal redirect just when my file is html, whit .php file doesn’t works :S
    U know why can be this error.
    Thanks 😉



    Nicky

    hello,

    can this method login using account in radius?

    thanks



    Jahongir

    Hello How can I make the same way but without two interfaces. Just plug Pi with dhcp server to TP-Link with wan Internet and disable DHCP on TP-link router?



      awippler

      You would use VLans. You need to make the RPi the main router. A bit advanced, but do-able if you know what you are doing.



    Vysakh

    Hello Andrew
    This was really helpful.The only problem I am facing now is in connecting my device to the WiFi.The SSID is being displayed and its being shown as an open network .Why am I unable to connect to the network ?



    Peter

    Don’t know why but
    exec(“sudo rmtrack ” . $_SERVER[‘REMOTE_ADDR’]);
    within the php script results in endless loading for me.
    I, however, can do
    rmtrack 192.168.178.2
    on my console without any problem.
    I added
    www-data ALL = NOPASSWD: /usr/bin/rmtrack [0-9]*.[0-9]*.[0-9]*.[0-9]*
    to the corresponding sudoers file and the rest works but not that.
    Any idea? I already tried to
    > /dev/null &
    the output, it looks like the awk part makes a problem to be executed from within php.



    Vineet

    And wht if i want to remove the device from the firewall(disconnect it)
    jst change the –set-mark??



    Christian

    Hello Andrew, your work is great.
    I was tried your code and it works perfectly.
    I just have a question, I want to include facebook oauth (later other social logins), but I tried put facebook.com in this line
    # allow access to my website 🙂
    iptables -t filter -A wlan0_Global -d facebook.com -j ACCEPT
    But any request for other pages goes to index of nginx.
    I think the prevails this rules always.
    iptables -t nat -A wlan0_Unknown -p tcp –dport 80 -j DNAT –to-destination 192.168.100.1
    How I can put a rule that only for facebook.com not redirect to index page, and I can go to facebook.com for Login.
    Thanks for your help.
    Regards,
    Christian



    Christian

    in complement for my previous post, I tried put the URL in wlan0_AuthServers and I have same results, requests to new IP in auth servers goes to nginx local server with 404 not found.
    Can you help me for I get auth from facebook and other form in the cloud.
    Thanks in advance for your help.
    Regards,
    Christian



    andrei

    hello Andrew, you have an error in your config:

    May 25 14:18:00 raspberrypi nginx[2001]: nginx: [emerg] invalid condition “~*” in /etc/nginx/sites-enabled/hotspot.conf:8

    server {
    # Listening on IP Address.
    # This is the website iptables redirects to
    listen 80 default_server;
    root /usr/share/nginx/html/portal;

    # For iOS
    if ( ~* (CaptiveNetworkSupport) ) {
    return 302 http://hotspot.localnet/hotspot.html;
    }

    # For others
    location / {
    return 302 http://hotspot.localnet/;
    }
    }



    jr

    thanks for the writeup, any suggestions regarding how to do hotspot.conf functionality with apache2 would be appreciated



    Dave

    I the Error which andrei said still exists, if you comment the iOS section out, it works though.

    It doesn’t seem to work with a .php File, but if I add an html file with a meta redirection to the php, i get a 502 error 🙁
    If I copy the content from the php to the html, it works but the page itself doesn’t work. (of course, its html instead of php.)

    also the portal is not really captive, If i try to connect to google.com it does not load the content at all, but does not give a “too much redirects” error either.

    please help 😀



      Dave

      Me again. 😀
      502 error fix:

      [from wildlyinaccurate.com/solving-502-bad-gateway-with-nginx-php-fpm/]

      Next, get php-fpm to listen on the correct host/port. In /etc/php5/fpm/pool.d/www.conf change the listen value to match the fastcgi_pass location in your Nginx configuration. For example, I changed mine from:

      listen = /var/run/php5-fpm.sock

      To:

      listen = 127.0.0.1:9000

      now your .php works again.

      and the captive thing was my own fault, my wifi adapter is not wlan0 but a long combination of stuff 😀
      thanks for the tutorial by the way ! 🙂

      (haven’t found the bug for the iOS section though)



    Alexander Zubkov

    ssid=MyOpenAP # name of the WiFi access point

    You need to remove the comment, the whole line is interpreted as ssid name and does not work.



    DoM!niC

    Hey Guys,
    sombody can tell me how to route my Squid3 when the client mark to access to all?

    Currenly I used /sbin/iptables -t nat -A PREROUTING -p tcp -s 10.1.0.0/24 –dport 80 -j REDIRECT –to-port 3128 but with portal it doesn’t work



      DoM!niC

      I figured it how to route & include my port forwarding stuff to have more control about whitelisted ports. But someone could tell how to remove the marked devices like do a Time Conditions?



    rajiv

    It is giving me error on reloading nginx. the error is “invalid host in upstream 127.0.0.1/9000.



    rajiv

    I get error on reloading nginx. the error is “invalid host in upstream 127.0.0.1/9000.



    rajiv

    get an error on reloading nginx- invalid host in upstream 127.0.0.0/9000.



    OrcsRiver

    Hi Andrew, this is awesome! I tried it on my RPi3 (built-in wlan0), works great only if the RPi is pluged to eth0 i.e has internet access.
    The CaptivePortal popup doesnt show-up if the RPi has no internet access. I need to use the RPi to communicate/inform people in remote areas. Please help.


Add to the conversation:

Your email address will not be published. Required fields are marked *