{"id":6186,"date":"2026-09-05T14:10:45","date_gmt":"2026-09-05T05:10:45","guid":{"rendered":"https:\/\/eternalsphere.net\/echoes\/?p=6186"},"modified":"2026-09-12T17:17:53","modified_gmt":"2026-09-12T08:17:53","slug":"abxs3ewis43j9ou","status":"publish","type":"post","link":"https:\/\/blog.eternalsphere.net\/index.php\/2026\/09\/05\/abxs3ewis43j9ou\/","title":{"rendered":"Designing Reliable Multi-Uplink Networking on a Raspberry Pi with dhcpcd, Wi-Fi, and USB Cellular Fallback"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">A Raspberry Pi used as a portable server or remote-management node often needs more than one way to reach the network. A practical design may include three independent uplinks:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>wired Ethernet,<\/li>\n\n\n\n<li>local Wi-Fi,<\/li>\n\n\n\n<li>a USB cellular or tethering device.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">The challenge is not merely getting all three interfaces online. The system should also have a predictable priority order, survive reboots, tolerate changing USB interface names, and remain remotely reachable even when no known Wi-Fi network is available.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This article describes a robust design based on:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Ethernet\n   \u2193\nWi-Fi\n   \u2193\nUSB cellular<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">with automatic route selection and a stable logical name for the cellular interface.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">All identifying information below has been replaced with generic placeholders.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h2 class=\"wp-block-heading\">1. Target Architecture<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The desired behavior is:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"> <code>               \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n                \u2502    Raspberry Pi      \u2502\n                \u2502                      \u2502\n                \u2502  eth0     metric 100 \u2502\n                \u2502  wlan0    metric 200 \u2502\n                \u2502  cell0    metric 300 \u2502\n                \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n                           \u2502\n              lowest metric wins\n                           \u2502\n          \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n          \u2502                \u2502                \u2502\n       Ethernet          Wi-Fi        USB cellular\n       preferred        secondary       fallback<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The final priority is:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Interface<\/th><th>Purpose<\/th><th>Metric<\/th><th>Priority<\/th><\/tr><\/thead><tbody><tr><td><code>eth0<\/code><\/td><td>Wired Ethernet<\/td><td>100<\/td><td>Highest<\/td><\/tr><tr><td><code>wlan0<\/code><\/td><td>Local Wi-Fi<\/td><td>200<\/td><td>Second<\/td><\/tr><tr><td><code>cell0<\/code><\/td><td>USB cellular\/tethering<\/td><td>300<\/td><td>Last fallback<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Linux normally prefers the route with the <strong>lowest metric<\/strong> when several default routes are available.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Therefore:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>100 &lt; 200 &lt; 300<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">means:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Ethernet \u2192 Wi-Fi \u2192 Cellular<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h1 class=\"wp-block-heading\">2. Identifying the Actual Network Stack<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">A common mistake on modern Ubuntu systems is to assume that NetworkManager or <code>systemd-networkd<\/code> is managing the interfaces.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">That assumption should always be verified.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A system may instead be using:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>dhcpcd\n+\nwpa_supplicant<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The roles are different.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><code>dhcpcd<\/code><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><code>dhcpcd<\/code> handles:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>DHCP address acquisition,<\/li>\n\n\n\n<li>gateways,<\/li>\n\n\n\n<li>routing,<\/li>\n\n\n\n<li>route metrics,<\/li>\n\n\n\n<li>DNS information,<\/li>\n\n\n\n<li>interface lifecycle.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\"><code>wpa_supplicant<\/code><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><code>wpa_supplicant<\/code> handles:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Wi-Fi authentication,<\/li>\n\n\n\n<li>WPA\/WPA2\/WPA3 association,<\/li>\n\n\n\n<li>connection to an access point.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">A typical diagnostic may reveal:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>dhcpcd.service          active\nwpa_supplicant.service  active\n\nNetworkManager          inactive\nsystemd-networkd        inactive or masked<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">At that point, route-priority configuration should be made in the configuration actually used by <code>dhcpcd<\/code>, represented here as:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;DHCPCD_CONFIG&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">rather than modifying an inactive NetworkManager profile or an unused networkd file.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h1 class=\"wp-block-heading\">3. Understanding the USB Cellular Interface<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Many USB cellular dongles do not appear to Linux as a traditional modem.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Instead, they behave like a miniature router connected over USB.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>USB device\n   \u2193\nRNDIS \/ CDC Ethernet\n   \u2193\nLinux sees an Ethernet-like interface\n   \u2193\nDHCP\n   \u2193\nprivate address + gateway<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Linux may detect it with a driver such as:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>rndis_host<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">and assign a name similar to:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>enx&lt;MAC-derived-name&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The USB device itself may internally provide:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Host address:    &lt;USB_PRIVATE_IP&gt;\nGateway:         &lt;USB_PRIVATE_GATEWAY&gt;\nDNS:             &lt;USB_PRIVATE_GATEWAY&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">From Linux&#8217;s perspective, this is simply another routed Ethernet interface.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This also explains why tools such as ModemManager may report:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>No modems were found<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">even though cellular Internet connectivity works perfectly.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The modem functionality is hidden behind the USB device&#8217;s own embedded router.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h1 class=\"wp-block-heading\">4. The Problem with MAC-Derived <code>enx...<\/code> Names<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">A Linux USB Ethernet interface may initially appear as:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>enx&lt;DEVICE_MAC&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">For ordinary USB Ethernet adapters, this is often stable.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Some cellular or tethering devices, however, can present a different MAC address after:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>reboot,<\/li>\n\n\n\n<li>USB power cycle,<\/li>\n\n\n\n<li>firmware reset,<\/li>\n\n\n\n<li>network restart,<\/li>\n\n\n\n<li>device replacement.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">That creates a serious configuration problem.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Suppose the configuration contains:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>interface enx123456789abc\nmetric 300<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">and after the next USB reset Linux creates:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>enxabcdef123456<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The route rule no longer applies.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The physical device is still working, but the operating system sees it under a different name.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h1 class=\"wp-block-heading\">5. Why a Wildcard Is Not a Reliable Solution<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">It may be tempting to write:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>interface enx*\nmetric 300<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">However, a configuration syntax that accepts an interface name does not necessarily treat shell-style wildcards as expected.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A useful diagnostic clue is the resulting route metric.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Suppose the intended configuration is:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>metric 200<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">but the actual route becomes:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>metric 1003<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">That strongly suggests that the custom rule did not match, and <code>dhcpcd<\/code> assigned its normal automatic metric instead.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">So a wildcard such as:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>enx*<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">should not be relied upon unless the exact behavior of the installed <code>dhcpcd<\/code> version has been verified.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A cleaner design is to solve the naming problem itself.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h1 class=\"wp-block-heading\">6. Creating a Stable Logical Interface Name<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">The better abstraction is:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>physical USB device\n        \u2193\nudev \/ systemd link rule\n        \u2193\ncell0\n        \u2193\ndhcpcd<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Instead of teaching routing rules about every possible <code>enx...<\/code> name, the USB cellular interface is renamed to a stable logical name:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cell0<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The physical name becomes irrelevant.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h1 class=\"wp-block-heading\">7. Identifying the USB Network Device<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Before creating the naming rule, the interface should be inspected.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Useful properties include:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ID_NET_DRIVER=&lt;USB_NETWORK_DRIVER&gt;\nID_VENDOR_ID=&lt;USB_VENDOR_ID&gt;\nID_MODEL_ID=&lt;USB_MODEL_ID&gt;\nID_MODEL=&lt;DEVICE_MODEL&gt;\nID_PATH=&lt;USB_TOPOLOGY_PATH&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">For example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ID_NET_DRIVER=rndis_host\nID_VENDOR_ID=&lt;VENDOR_ID&gt;\nID_MODEL_ID=&lt;PRODUCT_ID&gt;\nID_MODEL=&lt;CELLULAR_DEVICE_MODEL&gt;\nID_PATH=&lt;USB_PATH&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">These properties are generally much more useful for identifying the device than its MAC-derived interface name.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h1 class=\"wp-block-heading\">8. Creating the <code>.link<\/code> Rule<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">A custom <code>systemd.link<\/code> rule can rename the interface before higher-level networking configures it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Represent the custom file as:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;SYSTEMD_LINK_RULE&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">A port-specific rule could look like:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&#091;Match]\nDriver=rndis_host\nPath=&lt;USB_PATH_PATTERN&gt;\n\n&#091;Link]\nNamePolicy=\nName=cell0<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The important result is:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>changing enx... name\n        \u2193\nalways becomes\n        \u2193\ncell0<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">A dry test should show something equivalent to:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Config file &lt;SYSTEMD_LINK_RULE&gt; is applied\nPolicies didn't yield a name, using specified Name=cell0\nID_NET_NAME=cell0<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">That confirms that the rename rule matches before a reboot is performed.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h1 class=\"wp-block-heading\">9. USB-Port-Dependent vs Hardware-Dependent Matching<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">There are two main ways to identify the device.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Port-based matching<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&#091;Match]\nDriver=rndis_host\nPath=&lt;USB_PORT_PATH&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Advantages:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>highly deterministic,<\/li>\n\n\n\n<li>simple,<\/li>\n\n\n\n<li>avoids accidentally matching another similar device.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Disadvantage:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>moving the dongle to another physical USB port changes the path.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">This means the rule may stop matching.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h2 class=\"wp-block-heading\">Hardware-based matching<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A more portable rule can match stable USB properties instead of the physical port.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Conceptually:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&#091;Match]\nDriver=&lt;USB_NETWORK_DRIVER&gt;\nProperty=ID_VENDOR_ID=&lt;VENDOR_ID&gt; ID_MODEL_ID=&lt;PRODUCT_ID&gt;\n\n&#091;Link]\nNamePolicy=\nName=cell0<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This design allows the device to be moved between USB ports while preserving:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cell0<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">It is usually preferable when USB-port flexibility matters.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h1 class=\"wp-block-heading\">10. Route Priority Configuration<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Once the cellular device is permanently represented as <code>cell0<\/code>, the routing configuration becomes very simple.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The relevant portion of <code>&lt;DHCPCD_CONFIG&gt;<\/code> can be:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Wired Ethernet - highest priority\ninterface eth0\nmetric 100\n\n# Wi-Fi - second priority\ninterface wlan0\nmetric 200\n\n# USB cellular - final fallback\ninterface cell0\nmetric 300<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This creates a clean abstraction:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>physical modem identity\n      handled by .link rule\n\nrouting priority\n      handled by dhcpcd<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The two problems are separated.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">That is much cleaner than mixing USB hardware names directly into route configuration.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h1 class=\"wp-block-heading\">11. Safe Configuration Backups<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Before modifying network configuration on a remotely managed server, a copy should be made.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A simple timestamp-only convention avoids ambiguity:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;DHCPCD_CONFIG&gt;.&lt;YYYYMMDD-HHMMSS&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;DHCPCD_CONFIG&gt;.20260905-135542<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Likewise:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;SYSTEMD_LINK_RULE&gt;.20260905-140102<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">No extra words such as:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>.bak\n.backup\n.old<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">are necessary.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This provides chronological versioning while keeping filenames concise.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h1 class=\"wp-block-heading\">12. Reboot Verification<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">After applying the stable interface name and route metrics, a reboot provides the cleanest full test.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Immediately after boot, suppose:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>eth0   disconnected\nwlan0  disconnected\ncell0  connected<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The routing table should contain only:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>default via &lt;CELLULAR_GATEWAY&gt; dev cell0 metric 300<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">An actual route lookup should also show:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;TEST_PUBLIC_IP&gt; via &lt;CELLULAR_GATEWAY&gt; dev cell0<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This confirms that the cellular link independently provides management connectivity.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h1 class=\"wp-block-heading\">13. Bringing Wi-Fi Online<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">If Wi-Fi is then connected manually, the routing table should change automatically:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>default via &lt;WIFI_GATEWAY&gt;     dev wlan0 metric 200\ndefault via &lt;CELLULAR_GATEWAY&gt; dev cell0 metric 300<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">A route lookup should now select:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>dev wlan0<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">because:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>200 &lt; 300<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">No manual route deletion is required.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>dhcpcd<\/code> manages both routes simultaneously and Linux selects the preferred one.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h1 class=\"wp-block-heading\">14. Why Manual Wi-Fi Can Be the Better Design<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">For a portable Raspberry Pi, automatically reconnecting Wi-Fi is not always desirable.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A system that moves between:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>home,<\/li>\n\n\n\n<li>office,<\/li>\n\n\n\n<li>hotel,<\/li>\n\n\n\n<li>temporary worksite,<\/li>\n\n\n\n<li>remote installation,<\/li>\n\n\n\n<li>customer location,<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">often encounters networks it has never seen before.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In such an environment, a better operational model is:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Boot\n \u2193\nUSB cellular comes online automatically\n \u2193\nSSH remains available\n \u2193\noperator connects remotely\n \u2193\nlocal Wi-Fi is selected manually\n \u2193\nwlan0 appears with metric 200\n \u2193\ntraffic automatically moves from cell0 to wlan0<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This avoids depending on a previously stored Wi-Fi profile for remote access.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The cellular connection acts as a <strong>management lifeline<\/strong>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h1 class=\"wp-block-heading\">15. Final Runtime Behavior<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">With all three interfaces configured, the intended behavior is:<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Only cellular available<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>cell0 metric 300<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Result:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cell0 is used<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h3 class=\"wp-block-heading\">Wi-Fi and cellular available<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>wlan0 metric 200\ncell0 metric 300<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Result:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>wlan0 is used\ncell0 remains available as fallback<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h3 class=\"wp-block-heading\">Ethernet, Wi-Fi, and cellular available<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>eth0  metric 100\nwlan0 metric 200\ncell0 metric 300<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Result:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>eth0 is used\nwlan0 remains secondary\ncell0 remains final fallback<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The complete hierarchy becomes:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"> <code>            \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n             \u2502    eth0       \u2502\n             \u2502  metric 100   \u2502\n             \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n                     \u2502\n                 preferred\n                     \u2502\n             \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25bc\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n             \u2502    wlan0      \u2502\n             \u2502  metric 200   \u2502\n             \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n                     \u2502\n                  fallback\n                     \u2502\n             \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25bc\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n             \u2502    cell0      \u2502\n             \u2502  metric 300   \u2502\n             \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h1 class=\"wp-block-heading\">16. Replacing the USB Cellular Device Later<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">The abstraction around <code>cell0<\/code> makes future hardware replacement much easier.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The rest of the system continues to reference:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cell0<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Therefore this does not need to change:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>interface cell0\nmetric 300<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Only the rule that maps physical hardware to <code>cell0<\/code> may need modification.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The replacement workflow is:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>new USB device\n    \u2193\ninspect driver \/ vendor \/ product \/ path\n    \u2193\nupdate &lt;SYSTEMD_LINK_RULE&gt;\n    \u2193\nnew device is named cell0\n    \u2193\nexisting dhcpcd configuration continues working<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The routing configuration does not care whether the underlying device is:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>old modem\nnew modem\nphone tethering adapter\nUSB router<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">as long as the new network device is mapped to:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cell0<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h1 class=\"wp-block-heading\">17. Different USB Networking Technologies<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">A replacement device may not use RNDIS.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Possible drivers include:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>rndis_host\ncdc_ether\ncdc_ncm\ncdc_mbim\nqmi_wwan<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The first three often expose an Ethernet-like interface directly.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Devices using:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cdc_mbim\nqmi_wwan<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">may require a different architecture involving tools such as:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ModemManager\nMBIM\nQMI<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">In that case, merely renaming the interface may not be sufficient.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">However, for ordinary USB tethering devices that expose DHCP-based Ethernet networking, the <code>cell0<\/code> abstraction remains a very effective design.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h1 class=\"wp-block-heading\">18. DNS Behavior<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Each uplink may advertise its own DNS server.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Ethernet:\nDNS = &lt;ETHERNET_DNS&gt;\n\nWi-Fi:\nDNS = &lt;WIFI_DNS&gt;\n\ncell0:\nDNS = &lt;CELLULAR_DNS&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><code>systemd-resolved<\/code> may retain DNS information for more than one active link.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This is related to, but separate from, IPv4 route priority.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A routing table may clearly prefer:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>wlan0 metric 200<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">over:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cell0 metric 300<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">while <code>systemd-resolved<\/code> still knows DNS servers for both interfaces.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For most ordinary configurations this is acceptable, but DNS failover should be evaluated separately if strict per-interface DNS routing is required.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h1 class=\"wp-block-heading\">19. Route Metrics Are Not Full Link Health Monitoring<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">Metric-based failover works very well when an interface or default route disappears.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Wi-Fi disconnects\n    \u2193\nwlan0 route disappears\n    \u2193\ncell0 route becomes preferred<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">However, there is an important limitation.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Suppose Wi-Fi remains associated and retains:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>IP address\ngateway\ndefault route<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">but the upstream Internet connection behind the Wi-Fi router is broken.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Linux still sees:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>wlan0 metric 200<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">and may continue trying to use it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Route metrics alone do not continuously verify Internet reachability.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">True upstream health checking requires additional logic such as:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>periodic connectivity probe\n        \u2193\nroute policy adjustment\n        \u2193\nautomatic uplink failover<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">For many portable-server deployments, ordinary interface-level failover is sufficient. More advanced health-based routing should only be added when genuinely required.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h1 class=\"wp-block-heading\">20. Why This Architecture Works Well<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">The final design separates four responsibilities cleanly:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>wpa_supplicant\n    \u2514\u2500 Wi-Fi authentication\n\ndhcpcd\n    \u2514\u2500 DHCP + route metrics\n\nsystemd .link \/ udev\n    \u2514\u2500 stable interface naming\n\nLinux routing table\n    \u2514\u2500 uplink selection<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This separation produces several benefits.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Stable configuration<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The routing layer no longer depends on changing USB MAC-derived names.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Predictable failover<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Priority is explicit:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>100 \u2192 Ethernet\n200 \u2192 Wi-Fi\n300 \u2192 Cellular<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Remote recoverability<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The USB cellular connection can provide immediate SSH access after boot even when no known Wi-Fi network exists.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Portable deployment<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Wi-Fi can be selected manually according to the current location.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Easy hardware replacement<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Only the physical-to-logical naming rule must be adapted when the USB tethering hardware changes.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h1 class=\"wp-block-heading\">Conclusion<\/h1>\n\n\n\n<p class=\"wp-block-paragraph\">A reliable multi-uplink Raspberry Pi does not require a complicated routing framework when the requirements are straightforward.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The key is to separate physical hardware identity from logical networking policy.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The resulting architecture is:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Physical hardware\n      \u2193\nstable logical names\n      \u2193\ndhcpcd route metrics\n      \u2193\nLinux route selection<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">with the final policy:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Ethernet  metric 100\nWi-Fi     metric 200\nCellular  metric 300<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">and the USB cellular device permanently represented as:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cell0<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This turns a potentially fragile combination of wired networking, changing Wi-Fi environments, and unstable USB interface names into a simple and maintainable failover system suitable for a portable Linux server.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>A Raspberry Pi used as a portable server or remote-management node often needs more than one way to reach the network. A practical design may include three independent uplinks: The challenge is not merely getting all three interfaces online. The system should also have a predictable priority order, survive reboots, &hellip;<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[120],"tags":[295],"class_list":["post-6186","post","type-post","status-publish","format-standard","hentry","category-1s3b6h7r2zay02x","tag-wireless"],"_links":{"self":[{"href":"https:\/\/blog.eternalsphere.net\/index.php\/wp-json\/wp\/v2\/posts\/6186","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.eternalsphere.net\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.eternalsphere.net\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.eternalsphere.net\/index.php\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.eternalsphere.net\/index.php\/wp-json\/wp\/v2\/comments?post=6186"}],"version-history":[{"count":1,"href":"https:\/\/blog.eternalsphere.net\/index.php\/wp-json\/wp\/v2\/posts\/6186\/revisions"}],"predecessor-version":[{"id":6187,"href":"https:\/\/blog.eternalsphere.net\/index.php\/wp-json\/wp\/v2\/posts\/6186\/revisions\/6187"}],"wp:attachment":[{"href":"https:\/\/blog.eternalsphere.net\/index.php\/wp-json\/wp\/v2\/media?parent=6186"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.eternalsphere.net\/index.php\/wp-json\/wp\/v2\/categories?post=6186"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.eternalsphere.net\/index.php\/wp-json\/wp\/v2\/tags?post=6186"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}