Skip to content

RouterOS Hotspot: Walled Garden, Custom Splash Page, and Access Policies

RouterOS Hotspot: Walled Garden, Custom Splash Page, and Access Policies

Section titled “RouterOS Hotspot: Walled Garden, Custom Splash Page, and Access Policies”
Configuration Guidemd-7law
DateMarch 22, 2026
RouterOS Version7.x

This guide covers advanced Hotspot configuration beyond basic setup:

  • Walled garden — allow specific hosts or IPs before login
  • Custom splash page — replace the built-in login HTML with branded templates
  • HTTPS redirect — enforce TLS on the login page
  • Rate limits — cap per-profile and per-user bandwidth
  • Trial authentication — grant time-limited access without credentials
  • Voucher authentication — distribute pre-created username/password credentials

For initial Hotspot setup (wizard, server profile, DHCP pool) see the Hotspot Captive Portal Gateway Guide.


The walled garden defines what unauthenticated clients can reach before logging in. RouterOS provides two separate tables matched in order:

TablePathMatch layer
HTTP walled garden/ip hotspot walled-gardenHTTP Host header + URL path (L7)
IP walled garden/ip hotspot walled-garden ipIP, protocol, port (L3/L4)

Rules in each table are evaluated top-to-bottom; the first match wins.

Use for plain-HTTP destinations matched by hostname or path. Wildcards (*) are supported in dst-host and dst-path.

/ip hotspot walled-garden
# Allow entire domain (HTTP only)
add dst-host=example.com action=accept comment="Registration portal"
# Allow wildcard subdomain
add dst-host=*.example.com action=accept comment="All subdomains of example.com"
# Allow specific path only
add dst-host=pay.example.com dst-path=/checkout* \
action=accept comment="Payment checkout page"
# Explicitly deny a host (useful to override a broad wildcard)
add dst-host=ads.example.com action=deny comment="Block ad subdomain"

HTTPS limitation: The HTTP walled garden cannot match HTTPS destinations by hostname alone — TLS prevents inspection of the Host header without termination. Use the IP walled garden for HTTPS resources.

Use for HTTPS destinations, non-HTTP protocols, and CDN backend IPs. Rules match destination address, protocol, and port independently.

/ip hotspot walled-garden ip
# Allow HTTPS to a specific IP
add dst-address=198.51.100.10 protocol=tcp dst-port=443 \
action=accept comment="Payment gateway HTTPS"
# Allow all traffic to a subnet (e.g. local NTP server)
add dst-address=203.0.113.0/24 action=accept \
comment="Internal services subnet"
# Allow DNS to a specific resolver
add dst-address=1.1.1.1 protocol=udp dst-port=53 \
action=accept comment="Allow Cloudflare DNS pre-auth"
# Allow OS captive-portal detection endpoints
add dst-address=204.79.197.200 protocol=tcp dst-port=80 \
action=accept comment="Microsoft NCSI detection"
add dst-address=142.250.0.0/15 protocol=tcp dst-port=80 \
action=accept comment="Google Captive Portal detection"

HTTP walled garden (/ip hotspot walled-garden):

PropertyDescription
dst-hostHTTP Host header to match; supports * wildcard
dst-pathURL path to match; supports * wildcard
methodHTTP method (GET, POST, etc.); omit to match all
actionaccept or deny
commentOptional description

IP walled garden (/ip hotspot walled-garden ip):

PropertyDescription
dst-addressDestination IP or subnet
src-addressSource IP or subnet (restricts which clients the rule applies to)
protocoltcp, udp, icmp, or blank for any
dst-portDestination port or range (requires protocol)
actionaccept, reject, or drop
# Show HTTP walled garden entries
/ip hotspot walled-garden print
# Show IP walled garden entries
/ip hotspot walled-garden ip print
# Monitor active hotspot hosts (including unauthenticated)
/ip hotspot host print

Custom Splash Page (Login Page Customisation)

Section titled “Custom Splash Page (Login Page Customisation)”

RouterOS serves login pages from HTML template files. You can replace or modify these files to create a branded captive portal experience.

The built-in templates live on the router’s flash storage under /hotspot/. The files most commonly customised:

FileShown when
login.htmlUnauthenticated client is redirected to log in
logout.htmlUser clicks logout
status.htmlAuthenticated user opens the portal status page
error.htmlAuthentication fails
alogin.htmlAuto-login redirect after successful authentication
rlogin.htmlRedirect for clients needing login (alternative flow)
radvert.htmlPost-login advertisement or redirect

Files not present in a custom directory fall back to the built-in defaults.

RouterOS injects variables at render time using $(variable) syntax. These must be preserved in any custom template for authentication to work.

Authentication form variables:

VariableDescription
$(link-login-only)Form action URL — always use this, not a hardcoded path
$(link-orig)Original URL the client was trying to reach (for post-login redirect)
$(username)Pre-filled username (from cookie or MAC bypass)
$(chap-id)CHAP challenge ID (HTTP CHAP auth)
$(chap-challenge)CHAP challenge value (HTTP CHAP auth)

Display variables:

VariableDescription
$(error)Error message string after failed login attempt
$(ip)Client IP address
$(mac)Client MAC address
$(session-time-left)Remaining session time for authenticated users
$(uptime)Session uptime

Conditional blocks:

$(if error)<p class="error">$(error)</p>$(endif)
$(if username)<p>Welcome back, $(username)</p>$(endif)

This is a stripped-down but fully functional login page. All hidden fields are required.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Network Login</title>
<style>
body { font-family: sans-serif; max-width: 400px; margin: 60px auto; padding: 1rem; }
.error { color: #c00; }
input[type=text], input[type=password] { width: 100%; padding: 8px; margin: 4px 0 12px; }
button { width: 100%; padding: 10px; background: #0068c9; color: #fff; border: none; cursor: pointer; }
</style>
</head>
<body>
<h2>Sign in to continue</h2>
$(if error)<p class="error">$(error)</p>$(endif)
<form action="$(link-login-only)" method="post" accept-charset="UTF-8">
<input type="hidden" name="chap-id" value="$(chap-id)">
<input type="hidden" name="chap-challenge" value="$(chap-challenge)">
<input type="hidden" name="dst" value="$(link-orig)">
<label>Username<br>
<input type="text" name="username" value="$(username)" autocomplete="username">
</label>
<label>Password<br>
<input type="password" name="password" autocomplete="current-password">
</label>
<button type="submit">Log In</button>
</form>
</body>
</html>
  1. Create the directory and upload your files via Winbox → Files, FTP, or SCP:

    # Files uploaded to flash:/hotspot_custom/login.html etc.
  2. Point the server profile at the custom directory:

    /ip hotspot profile
    set [find name=hsprof1] html-directory-override=hotspot_custom
  3. Verify the profile is updated:

    /ip hotspot profile print where name=hsprof1

Partial override: Only files present in the custom directory are used; missing files fall back to the built-in defaults. You can ship only login.html and leave the rest as defaults.


Enabling HTTPS redirect sends clients to the login page over TLS, preventing credential exposure on the captive portal login.

A certificate with a CN matching the hotspot dns-name is required. Generate a self-signed certificate if no public certificate is available:

/certificate
add name=hotspot-ca common-name=hotspot-ca key-size=2048 \
days-valid=3650 key-usage=key-cert-sign,crl-sign
sign hotspot-ca ca=hotspot-ca
add name=hotspot-cert common-name=login.example.com key-size=2048 \
days-valid=3650 subject-alt-name=IP:10.5.50.1
sign hotspot-cert
/ip hotspot profile
set [find name=hsprof1] \
ssl-certificate=hotspot-cert \
login-by=https,http-chap,cookie
PropertyValuesDescription
ssl-certificatecertificate nameCertificate used for the HTTPS login page
login-bycomma-separated listInclude https to accept TLS-authenticated logins
  • Clients visiting HTTP destinations are intercepted and redirected to https://$(dns-name)/login. If the certificate is self-signed, the browser shows a trust warning.
  • HTTPS destinations (client’s initial request) cannot be intercepted and redirected by the Hotspot engine — the TLS handshake completes to the original server before RouterOS can inject a redirect. Use walled-garden IP rules to allow OS captive-portal detection endpoints so that HTTP-based probes still trigger the portal.
  • For production deployments, use a certificate signed by a public CA (obtained via Let’s Encrypt with a domain that points to the router’s WAN or a publicly reachable IP).

User profiles (/ip hotspot user profile) define bandwidth policy applied to authenticated sessions. Multiple profiles allow service tiers.

rx/tx[burst-rx/burst-tx[burst-threshold-rx/burst-threshold-tx[burst-time-rx/burst-time-tx[priority[min-rx/min-tx]]]]]

Units: k = kbit/s, M = Mbit/s, G = Gbit/s. rx is download (router→client), tx is upload (client→router).

Common examples:

5M/2M # 5 Mbit/s download, 2 Mbit/s upload, no burst
10M/5M 20M/10M # 10/5 sustained, burst to 20/10
10M/5M 20M/10M 8M/4M 10s # burst above 8/4 threshold for 10 seconds
/ip hotspot user profile
# Free tier: 2 Mbit/s symmetric, 30 minute sessions
add name=free \
rate-limit=2M/2M \
session-timeout=30m \
idle-timeout=10m \
shared-users=1
# Basic tier: 10 Mbit/s down, 5 up, 8h sessions
add name=basic \
rate-limit=10M/5M \
session-timeout=8h \
idle-timeout=30m \
shared-users=1
# Premium tier: 50 Mbit/s with burst, unlimited session
add name=premium \
rate-limit="50M/20M 100M/40M 40M/16M 10s" \
session-timeout=0 \
idle-timeout=1h \
shared-users=3

Note: rate-limit is a profile-level property only. It cannot be set on individual /ip hotspot user entries. To give a specific user a different bandwidth cap, assign them to a dedicated profile with the desired rate limit.

PropertyDescription
rate-limitBandwidth cap; overridden by user-level rate-limit
session-timeoutMaximum session duration; 0 = unlimited
idle-timeoutDisconnect after idle; 0 = disabled
shared-usersMax simultaneous logins with the same credentials
address-poolIP pool for clients using this profile
incoming-filterFirewall chain applied to inbound client traffic
outgoing-filterFirewall chain applied to outbound client traffic

Trial authentication allows clients to connect for a limited time without providing credentials. The client clicks a “Try for free” button (or the portal auto-submits) and receives a session governed by the trial profile.

/ip hotspot user profile
add name=trial \
rate-limit=1M/1M \
session-timeout=30m \
shared-users=1
/ip hotspot profile
set [find name=hsprof1] \
login-by=http-chap,http-pap,cookie,trial \
trial-uptime-limit=30m \
trial-uptime-reset=1d \
trial-user-profile=trial
PropertyDescription
login-by=trialEnables the trial login method for this server profile
trial-uptime-limitMaximum trial session duration per reset period (e.g. 30m, 1h)
trial-uptime-resetHow often the trial uptime counter resets per client (e.g. 1d) — resets at midnight by default
trial-user-profileUser profile applied to trial sessions; controls rate limit and timeouts

Add a second form that POSTs username=T- + client MAC to trigger the trial flow:

<!-- Trial / Guest access form -->
<form action="$(link-login-only)" method="post" accept-charset="UTF-8">
<input type="hidden" name="dst" value="$(link-orig)">
<input type="hidden" name="username" value="T-$(mac)">
<input type="hidden" name="password" value="$(mac)">
<button type="submit">Try for 30 minutes (no login required)</button>
</form>

RouterOS recognises the T-<mac> username pattern and maps it to the trial profile defined in the server profile. The trial counter is tracked per MAC address.

Trial usage is tracked in the active host table. To inspect remaining trial time:

/ip hotspot host print where comment~"trial"
/ip hotspot active print

Voucher authentication is standard username/password authentication where the credentials are pre-generated and printed or distributed as vouchers. RouterOS has no separate “voucher” object — a voucher is simply a Hotspot user account.

/ip hotspot user
# Single voucher — 1 hour, 1 device
add name=VCHR-0001 password=X7kP2m profile=basic \
limit-uptime=1h shared-users=1 comment="Day pass voucher"
# Bulk creation via script
:for i from=1 to=50 do={
:local name ("VCHR-" . [:tostr $i])
:local pass [/certificate scep-server generate-key]
/ip hotspot user add name=$name password=$pass \
profile=basic limit-uptime=1h shared-users=1
}
PropertyDescription
limit-uptimeTotal connected time before account expires (e.g. 1h, 1d)
limit-bytes-totalTotal data quota (upload + download) before expiry
limit-bytes-inDownload quota
limit-bytes-outUpload quota
shared-usersMax simultaneous sessions; set to 1 for single-device vouchers
disabledDisable the account without deleting it

When a voucher reaches its limit-uptime or byte quota:

  1. The active session is immediately terminated.
  2. The user account is not automatically deleted — it remains in the user list in an exhausted state.
  3. Subsequent login attempts with the same credentials are rejected.

To clean up used vouchers:

# Remove users whose uptime limit is exhausted
/ip hotspot user remove [find limit-uptime>0 uptime>=limit-uptime]

For larger deployments, User Manager provides a dedicated voucher generation UI and centrally manages quotas via RADIUS. See the User Manager guide for setup, then point the Hotspot server at User Manager:

/radius
add service=hotspot address=127.0.0.1 secret=shared-secret \
authentication-port=1812 accounting-port=1813
/ip hotspot profile
set [find name=hsprof1] use-radius=yes

# Active authenticated sessions
/ip hotspot active print
# All hosts, including unauthenticated and trial
/ip hotspot host print
# HTTP walled garden rules
/ip hotspot walled-garden print
# IP walled garden rules
/ip hotspot walled-garden ip print
# User profile list
/ip hotspot user profile print
# Check hotspot server profile settings
/ip hotspot profile print detail where name=hsprof1