DEV Community

Cover image for Learning OpenWrt in Incus: From a Virtual Router to LuCI Web Access
hardyweb
hardyweb

Posted on AI-assisted

Learning OpenWrt in Incus: From a Virtual Router to LuCI Web Access

Introduction

I wanted to learn how OpenWrt works as a router without buying additional hardware or disturbing my existing homelab network.

My approach was to run OpenWrt as an Incus container and connect a separate Debian container as its LAN client.

The exercise covered several practical networking concepts:

  • Creating an OpenWrt container.
  • Connecting WAN and LAN interfaces.
  • Configuring a static LAN address and DHCP.
  • Understanding firewall zones, forwarding, and NAT.
  • Troubleshooting Internet access in a network that requires an HTTP proxy.
  • Making the LuCI web interface accessible from the host using an Incus proxy device.

This is a learning lab, not a production router configuration.

1. Lab architecture

The topology consists of three main components:

  1. The Debian host running Incus.
  2. An OpenWrt container acting as the router.
  3. A Debian container acting as a LAN client.

The OpenWrt WAN interface connects to an existing Incus bridge. Its LAN interface connects to a newly created, isolated Incus network.

                 Existing upstream network
                           |
                    Incus host bridge
                           |
                      OpenWrt WAN
                           |
                    OpenWrt container
                      WAN | LAN
                          |
                    Isolated LAN bridge
                          |
                     Debian client

Browser on host
      |
127.0.0.1:8080
      |
Incus proxy device
      |
OpenWrt:80 (LuCI)
Enter fullscreen mode Exit fullscreen mode

Example IP addressing

The following table uses example addresses instead of the original lab addresses.

Component Example address Purpose
Incus upstream gateway 192.168.50.1 Existing upstream network
OpenWrt WAN 192.168.50.10 Router's upstream interface
OpenWrt LAN 192.168.60.1 Default gateway for LAN clients
Debian client 192.168.60.100 DHCP-assigned address
LuCI host endpoint 127.0.0.1:8080 Browser access from the host

These are illustrative private addresses. Your WAN address will normally be assigned by DHCP, so do not configure the example WAN address blindly.

The 192.0.2.0/24, 198.51.100.0/24, and 203.0.113.0/24 ranges are reserved for documentation. They are not substitutes for real, routable public IP addresses.

Security note: Do not publish your actual internal addressing, proxy address, credentials, or other sensitive network details unless you have a reason to disclose them.

2. Create the OpenWrt container

Start by launching an OpenWrt image:

incus launch images:openwrt/25.12 openwrt
Enter fullscreen mode Exit fullscreen mode

Check the container status:

incus list
incus info openwrt
Enter fullscreen mode Exit fullscreen mode

Inspect the network interfaces:

incus exec openwrt -- ip link
incus exec openwrt -- ip addr
incus exec openwrt -- ip route
Enter fullscreen mode Exit fullscreen mode

The OpenWrt image used in this lab was from the OpenWrt 25.12 series.

Image availability and package names can change between releases. Check the image and release documentation before reproducing this on another system.

3. Connect the WAN interface

The OpenWrt container initially connects to the existing Incus bridge.

In this lab, the WAN interface is named eth0 and uses DHCP.

Inspect the OpenWrt network configuration:

incus exec openwrt -- cat /etc/config/network
Enter fullscreen mode Exit fullscreen mode

The relevant configuration is:

config interface 'wan'
        option device 'eth0'
        option proto 'dhcp'
Enter fullscreen mode Exit fullscreen mode

Check the resulting address and route:

incus exec openwrt -- ip addr
incus exec openwrt -- ip route
Enter fullscreen mode Exit fullscreen mode

A working WAN configuration should have an address and a default route.

For example:

default via 192.168.50.1 dev eth0
Enter fullscreen mode Exit fullscreen mode

The gateway above is illustrative; use the route actually assigned in your environment.

Test connectivity to the upstream gateway:

incus exec openwrt -- ping -c 3 192.168.50.1
Enter fullscreen mode Exit fullscreen mode

Replace the example gateway with the actual one shown by ip route.

A successful gateway ping confirms local reachability to that gateway. It does not prove that unrestricted Internet access is available.

4. Create an isolated LAN network

Rather than modifying an existing Incus bridge, create a dedicated network for the OpenWrt LAN.

incus network create owrt-lan \
  ipv4.address=none \
  ipv6.address=none
Enter fullscreen mode Exit fullscreen mode

This creates a bridge without an Incus-managed IPv4 or IPv6 gateway address.

Attach a second NIC to OpenWrt:

incus config device add openwrt lan0 nic \
  network=owrt-lan \
  name=eth1
Enter fullscreen mode Exit fullscreen mode

Verify that the new interface exists:

incus exec openwrt -- ip link
Enter fullscreen mode Exit fullscreen mode

The intended interface layout is:

  • eth0: WAN
  • eth1: LAN

Interface names can vary by image and configuration. Confirm them before applying network settings.

5. Configure the OpenWrt LAN address

Configure the LAN interface with a static address.

For this example, the LAN subnet is 192.168.60.0/24, and the router address is 192.168.60.1.

incus exec openwrt -- ash -c '
uci set network.lan="interface"
uci set network.lan.device="eth1"
uci set network.lan.proto="static"
uci set network.lan.ipaddr="192.168.60.1"
uci set network.lan.netmask="255.255.255.0"
uci commit network
'
Enter fullscreen mode Exit fullscreen mode

Reload the network configuration:

incus exec openwrt -- /etc/init.d/network reload
Enter fullscreen mode Exit fullscreen mode

Verify the LAN address:

incus exec openwrt -- ip addr
incus exec openwrt -- ip route
Enter fullscreen mode Exit fullscreen mode

Expected route:

192.168.60.0/24 dev eth1 scope link
Enter fullscreen mode Exit fullscreen mode

This subnet is separate from the upstream network. Keeping the WAN and LAN subnets distinct is essential for a conventional routed configuration.

6. Enable DHCP for LAN clients

Configure OpenWrt to assign addresses to clients on the LAN.

incus exec openwrt -- ash -c '
uci set dhcp.lan="dhcp"
uci set dhcp.lan.interface="lan"
uci set dhcp.lan.start="100"
uci set dhcp.lan.limit="100"
uci set dhcp.lan.leasetime="12h"
uci set dhcp.lan.ignore="0"
uci commit dhcp
'
Enter fullscreen mode Exit fullscreen mode

The DHCP pool starts at the router's LAN address plus 100 addresses in the configured subnet. For this example, clients can receive addresses starting at 192.168.60.100.

Reload the relevant service:

incus exec openwrt -- /etc/init.d/dnsmasq restart
Enter fullscreen mode Exit fullscreen mode

Check that the service is running:

incus exec openwrt -- /etc/init.d/dnsmasq status
Enter fullscreen mode Exit fullscreen mode

Start a Debian client

incus launch images:debian/13 owrt-client \
  --network owrt-lan
Enter fullscreen mode Exit fullscreen mode

Inspect the client address and routing table:

incus exec owrt-client -- ip addr
incus exec owrt-client -- ip route
Enter fullscreen mode Exit fullscreen mode

The client should receive a DHCP address in the LAN subnet and a default route through 192.168.60.1.

Test the connection to OpenWrt:

incus exec owrt-client -- ping -c 3 192.168.60.1
Enter fullscreen mode Exit fullscreen mode

In this lab, DHCP worked and the client could ping the OpenWrt LAN address.

7. Understand the firewall and NAT configuration

OpenWrt uses firewall zones to control traffic between interfaces.

The relevant default design is:

  • LAN input: ACCEPT
  • LAN output: ACCEPT
  • LAN forwarding: ACCEPT
  • WAN input: REJECT
  • WAN output: ACCEPT
  • WAN forwarding: DROP
  • LAN-to-WAN forwarding: enabled
  • WAN masquerading: enabled for IPv4

Inspect the configuration:

incus exec openwrt -- uci show firewall
Enter fullscreen mode Exit fullscreen mode

Check the generated firewall rules:

incus exec openwrt -- fw4 print
Enter fullscreen mode Exit fullscreen mode

Look for a LAN-to-WAN forwarding rule and an IPv4 masquerading rule.

The conceptual flow is:

LAN client
    |
    | Source: 192.168.60.100
    v
OpenWrt LAN
    |
    | Forwarding: LAN -> WAN
    | IPv4 source NAT (masquerading)
    v
OpenWrt WAN
    |
    v
Upstream network
Enter fullscreen mode Exit fullscreen mode

The addresses are examples.

NAT allows multiple private LAN addresses to share an upstream IPv4 address. However, NAT is not an Internet access policy bypass. It cannot override an upstream firewall, an egress restriction, or a requirement to use an HTTP proxy.

8. Troubleshoot Internet access

This was the most instructive part of the exercise.

The Debian client could reach the OpenWrt router, and DNS resolution returned a result. However, direct HTTPS requests timed out.

Examples of the direct tests were:

incus exec owrt-client -- \
  curl -4 -I --connect-timeout 5 --max-time 8 \
  https://downloads.openwrt.org/
Enter fullscreen mode Exit fullscreen mode

The request timed out.

A direct IPv6 attempt also failed. That result alone does not establish why IPv6 was unavailable; the lab did not have a demonstrated global IPv6 route.

Ping tests to a public IPv4 address also received no replies:

incus exec owrt-client -- ping -c 3 1.1.1.1
incus exec openwrt -- ping -c 3 1.1.1.1
Enter fullscreen mode Exit fullscreen mode

Neither test received replies.

Ping failure is not conclusive proof of broken routing because ICMP may be filtered. Direct HTTPS timeouts are another useful signal, but they do not identify the precise cause by themselves.

Discover the upstream HTTP proxy

The host's environment used an HTTP proxy. For this article, the proxy address is intentionally represented as a placeholder:

<PROXY_HOST>:<PROXY_PORT>
Enter fullscreen mode Exit fullscreen mode

Do not copy the placeholder literally. Substitute a proxy that your network administrator has authorised you to use.

Test from the host:

curl -x http://<PROXY_HOST>:<PROXY_PORT> \
  -I --connect-timeout 5 --max-time 10 \
  https://downloads.openwrt.org/
Enter fullscreen mode Exit fullscreen mode

Then test from the LAN client:

incus exec owrt-client -- \
  curl -x http://<PROXY_HOST>:<PROXY_PORT> \
  -I --connect-timeout 5 --max-time 10 \
  https://downloads.openwrt.org/
Enter fullscreen mode Exit fullscreen mode

In the original lab, both requests returned HTTP/2 200.

The client could also download the page through the proxy:

incus exec owrt-client -- \
  curl -x http://<PROXY_HOST>:<PROXY_PORT> \
  -fL --connect-timeout 5 --max-time 20 \
  https://downloads.openwrt.org/ \
  -o /tmp/openwrt-index.html
Enter fullscreen mode Exit fullscreen mode

This downloaded the page successfully.

What did this prove?

It established that:

  1. The client could reach the proxy.
  2. The proxy could retrieve the requested HTTPS content.
  3. The client could use the proxy even though direct HTTPS access timed out.

It did not prove that every firewall or NAT path was correct, nor did it establish the exact reason direct traffic failed.

The important distinction is that an HTTP proxy is an application-layer service. Ordinary routing and NAT do not automatically redirect every HTTP or HTTPS connection through it.

For an HTTP CONNECT proxy, clients normally need explicit proxy configuration unless a separately designed and compatible interception mechanism is deployed.

Avoid placing proxy credentials in shell history, public repositories, screenshots, or published logs.

9. Install and start LuCI

LuCI is OpenWrt's web administration interface. The web server commonly used by OpenWrt is uhttpd.

Check whether the relevant components are installed:

incus exec openwrt -- sh -c '
opkg list-installed | grep -E "^(luci|uhttpd)"
'
Enter fullscreen mode Exit fullscreen mode

Check whether uhttpd is available and running:

incus exec openwrt -- sh -c '
command -v uhttpd
/etc/init.d/uhttpd status
'
Enter fullscreen mode Exit fullscreen mode

If LuCI is missing, package installation may be required.

incus exec openwrt -- opkg update
incus exec openwrt -- opkg install luci
Enter fullscreen mode Exit fullscreen mode

These commands require working package-repository access. In a proxy-restricted environment, opkg may need additional configuration or an alternative authorised package-delivery method. A successful curl through a proxy does not automatically configure opkg to use that proxy.

Once installed, enable and restart uhttpd:

incus exec openwrt -- /etc/init.d/uhttpd enable
incus exec openwrt -- /etc/init.d/uhttpd restart
Enter fullscreen mode Exit fullscreen mode

Check listening sockets:

incus exec openwrt -- netstat -lntp
Enter fullscreen mode Exit fullscreen mode

If netstat is unavailable, use whatever socket-inspection utility is included in the image.

Confirm the actual listening port and address before configuring the proxy device. This example assumes LuCI is available over HTTP on port 80.

10. Expose LuCI through an Incus proxy device

The goal is to open LuCI from the host without exposing the management interface to the entire LAN or Internet.

Use an Incus proxy device bound to the host's loopback interface.

On the Incus host:

incus config device add openwrt luci-proxy proxy \
  listen=tcp:127.0.0.1:8080 \
  connect=tcp:192.168.50.10:80
Enter fullscreen mode Exit fullscreen mode

Replace 192.168.50.10 with the actual OpenWrt WAN address assigned in your lab.

This command establishes the following connection:

Host browser
    |
    | http://127.0.0.1:8080
    v
Incus proxy device
    |
    | TCP forwarding
    v
OpenWrt WAN address:80
    |
    v
uhttpd / LuCI
Enter fullscreen mode Exit fullscreen mode

Because the listener is bound to 127.0.0.1, it is intended for access from the host itself.

Check the device configuration:

incus config device show openwrt
Enter fullscreen mode Exit fullscreen mode

Test it from the host:

curl -I --connect-timeout 5 http://127.0.0.1:8080/
Enter fullscreen mode Exit fullscreen mode

Then open the following address in a browser on the Incus host:

http://127.0.0.1:8080/
Enter fullscreen mode Exit fullscreen mode

The browser should reach LuCI if the interface is installed, uhttpd is listening on the configured destination, and the connection is permitted.

If the test fails, check the listener, destination address, uhttpd status, and firewall policy. OpenWrt normally restricts incoming connections from the WAN zone, so do not disable the firewall globally just to make LuCI work.

Important security considerations

  • Keep the proxy listener on 127.0.0.1 when only host access is required.
  • Do not bind LuCI to a public address.
  • Do not expose the OpenWrt administration interface directly to the Internet.
  • Use HTTPS and appropriate access controls when designing remote administration.
  • If other LAN computers need access, prefer a deliberate management network or a controlled tunnel rather than exposing the listener indiscriminately.

A loopback-only listener is not reachable directly from other computers on the network.

11. Troubleshooting checklist

When something fails, isolate the layer instead of changing several settings at once.

Container and interfaces

incus list
incus exec openwrt -- ip addr
incus exec openwrt -- ip route
Enter fullscreen mode Exit fullscreen mode

DHCP and client routing

incus exec owrt-client -- ip addr
incus exec owrt-client -- ip route
Enter fullscreen mode Exit fullscreen mode

Firewall

incus exec openwrt -- uci show firewall
incus exec openwrt -- fw4 print
Enter fullscreen mode Exit fullscreen mode

Proxy access

incus exec owrt-client -- \
  curl -x http://<PROXY_HOST>:<PROXY_PORT> \
  -I --connect-timeout 5 --max-time 10 \
  https://downloads.openwrt.org/
Enter fullscreen mode Exit fullscreen mode

LuCI

incus exec openwrt -- /etc/init.d/uhttpd status
incus config device show openwrt
curl -I --connect-timeout 5 http://127.0.0.1:8080/
Enter fullscreen mode Exit fullscreen mode

Replace the proxy placeholder and destination IP with values appropriate to your environment.

12. Lessons learned

This exercise reinforced several practical networking concepts.

A working LAN does not mean the Internet is reachable. DHCP, local routing, DNS, upstream routing, NAT, and proxy access are separate concerns.

NAT is not a proxy. Masquerading rewrites source addresses; it does not automatically route web requests through an HTTP proxy.

A failed ping does not always mean routing is broken. ICMP may be blocked, so test the actual service you need as well.

Use isolated networks for experiments. Creating a dedicated Incus bridge avoids changing unrelated homelab networks.

Management interfaces deserve extra protection. Binding a local port forward to 127.0.0.1 is a useful way to access LuCI from the host without publishing it to other machines.

Most importantly, troubleshoot with evidence. Inspect the routes, check the firewall configuration, test direct connectivity, test proxy connectivity, and verify the web server before changing the system.

Conclusion

Running OpenWrt inside Incus provided a practical way to explore router behaviour using existing hardware.

The lab progressed from a container with a WAN interface to an isolated LAN with DHCP, a Debian client, firewall forwarding, and a local LuCI access path.

The network's proxy requirement was a useful reminder that the real world does not always behave like a simple home-router diagram. Understanding the difference between routing, NAT, and application-layer proxies is just as important as getting the configuration to work.

For a learning lab, small experiments and careful verification are more valuable than making broad changes until something happens to work.

Top comments (0)