A small Linux host may have more than one way to reach the Internet:

  • wired Ethernet;
  • built-in Wi-Fi;
  • a USB network-sharing device;
  • sometimes additional tunnels or remote-access services.

The difficult part is usually not getting all of them online. The difficult part is making network selection predictable.

This system was designed around one simple priority:

Ethernet
   ↓
Known Wi-Fi
   ↓
USB network fallback

The USB network is always available. Wi-Fi is preferred when a trusted network is nearby, and Ethernet takes priority over everything else.

The important requirement was that the system should remain deterministic. It should not continuously scan, randomly switch networks, or make opaque decisions based on signal strength.

Existing routing priority

The Linux host already used explicit route metrics:

Ethernet     metric 100
Wi-Fi       metric 200
USB network metric 300

This meant the kernel already knew which connection to prefer.

When Wi-Fi and USB networking were both active:

Wi-Fi       metric 200
USB network metric 300

Wi-Fi naturally became the default route.

If Wi-Fi disappeared, the USB interface automatically became the preferred remaining route.

This led to an important design decision:

The fallback system should not actively “switch to USB.”

The USB connection should simply remain online at all times.

The Wi-Fi manager only needs to decide whether Wi-Fi should be connected.


Remember Wi-Fi credentials, not automatic behavior

The original Wi-Fi connection script behaved like this:

scan
→ select SSID
→ enter password
→ connect
→ discard temporary configuration

This worked, but required entering the password every time.

A more useful design is:

remember successful credentials
+
reuse them later

without turning Wi-Fi into an uncontrolled always-autoconnect system.

The host therefore keeps a small history database of Wi-Fi connections.


An SSID is not a unique network identity

A common mistake is to model Wi-Fi credentials like this:

SSID → password

That assumption breaks in several real situations.

Two unrelated access points can have the same SSID.

A router can also keep the same SSID while its password changes.

Instead, each successful credential is stored as an independent record:

SSID
security type
credential
first successful connection
most recent successful connection
success count
last observed BSSID

A single SSID may therefore have several credential records:

Network-A
├─ credential 1
├─ credential 2
└─ credential 3

Old credentials are not automatically deleted.

This is useful because an older credential may still belong to another network using the same SSID.


Most recently successful network wins

When several previously used Wi-Fi networks are visible at the same time, they are not selected by signal strength.

Instead, they are ordered by:

last successful connection time

For example:

Network A   last used yesterday
Network B   last used two months ago
Network C   last used six months ago

The system tries:

A
B
C

This creates predictable behavior while still naturally adapting to actual usage.

There is no separate learning algorithm.

The only rule is:

What worked most recently is tried first.


Multiple passwords for the same SSID

The same rule applies inside one SSID.

Suppose the database contains:

Network-A
├─ credential #3   last worked recently
├─ credential #2
└─ credential #1   oldest

The host tries all of them in that order:

#3
↓
#2
↓
#1

If credential #2 succeeds, its successful timestamp is updated.

The next time, #2 automatically moves to the front.

No additional priority system is required.


Failed credentials are not deleted

A failed authentication attempt does not necessarily mean the password is wrong.

Possible causes include:

  • weak signal;
  • temporary access-point failure;
  • the access point still starting;
  • a same-name network at another location;
  • wireless driver problems;
  • transient authentication errors.

Because of this, failure does not modify the historical ordering.

The system does not:

delete credential
lower priority
overwrite credential
mark permanently invalid

Only a successful connection changes the history.


Handling changed Wi-Fi passwords

If every saved credential for a selected SSID fails during a manual connection, the user is asked for a new password.

The new password is first tested.

Only after authentication and network configuration succeed is it written to the history database.

If the credential already exists, its timestamps are updated.

If it is genuinely new, a new record is created.

The previous records remain intact.

This makes password changes reversible and preserves useful history.


SQLite is a good fit

The credential history is stored in a small SQLite database.

The amount of data is tiny.

Even hundreds or thousands of historical Wi-Fi records are insignificant for a modern Linux system.

SQLite is preferable to a collection of text files because it naturally supports:

  • multiple credentials for one SSID;
  • sorting by timestamps;
  • uniqueness constraints;
  • Unicode SSIDs;
  • atomic updates;
  • future schema extensions.

A simplified record looks like:

id
ssid
security
psk
first_success
last_success
last_seen
success_count
last_bssid

The actual passphrase does not need to be stored.

For WPA-PSK networks, the derived PSK can be stored instead.


Boot-time logic

The final boot process is intentionally simple.

Boot
 ↓
Is Ethernet usable?
 ├─ Yes
 │    → do nothing
 │
 └─ No
      ↓
   scan Wi-Fi once
      ↓
   find visible networks with saved history
      ↓
   order SSIDs by most recent success
      ↓
   for each SSID:
       try every saved credential
       newest successful record first
      ↓
   first successful Wi-Fi wins
      ↓
   if everything fails:
       keep using USB fallback

The important word here is once.

The system does not continuously scan in the background.

There is no timer.

There is no periodic retry loop.

There is no daemon constantly changing the preferred network.

Each boot gets one deterministic network-selection pass.


Why continuous Wi-Fi scanning was avoided

A continuously running network selector can be convenient, but it also introduces uncertainty.

For example:

machine is using USB network
↓
a known Wi-Fi appears
↓
system silently changes the default route

That may disrupt long-lived connections or make troubleshooting harder.

For a server-like host, predictability is often more valuable than aggressive automation.

The chosen behavior is therefore:

boot once
decide once
leave the network alone

Manual reconnection remains available whenever needed.


Remote-access services should start afterward

The host also runs a persistent remote-access client.

Originally, it started as soon as the system considered networking online.

That creates a possible race:

USB network becomes available
↓
remote client connects
↓
Wi-Fi connects a few seconds later
↓
default route changes

The cleaner arrangement is:

base networking
↓
one-shot Wi-Fi selection
↓
remote-access client

The remote-access service does not require Wi-Fi to succeed.

If Wi-Fi fails completely, the USB network is still available and the remote service starts normally.

The dependency therefore represents ordering, not hard failure coupling.


One-shot systemd service

The automatic selector is run by a systemd oneshot service.

Conceptually:

[Service]
Type=oneshot
ExecStart=/path/to/wifi-connect --auto
RemainAfterExit=yes

RemainAfterExit=yes is useful because the boot decision should be considered complete after one successful execution.

Restarting another service later should not trigger another Wi-Fi scan.


Manual mode remains available

The same connection tool also supports interactive use.

Manual mode:

scan
↓
show nearby SSIDs
↓
user selects one
↓
try saved credentials first
↓
if all fail:
    ask for password
↓
if successful:
    update history

Automatic and manual modes therefore share the same underlying history.

This avoids having two independent systems that gradually behave differently.


Runtime secrets

Temporary Wi-Fi configuration should live under a runtime-only directory such as:

/run/...

rather than a general temporary directory.

The directory and configuration files should be root-only.

Credentials should also never appear in:

  • process arguments;
  • system logs;
  • diagnostic output;
  • shell history.

Only record identifiers and SSID names need to appear in operational logs.

For example:

Trying saved record #4 for SSID: Network-A
Saved record #4 succeeded

No credential material needs to be shown.


Final behavior

The resulting network behavior is:

Ethernet available
→ use Ethernet

No Ethernet
→ scan once for previously used Wi-Fi

Known Wi-Fi succeeds
→ use Wi-Fi

No known Wi-Fi works
→ USB network remains active

The routing layer and decision layer reinforce each other:

Ethernet metric     lower
Wi-Fi metric        middle
USB fallback metric higher

The selector determines which Wi-Fi connection exists.

The kernel determines which active interface wins.


Why this design works well

The system combines convenience with explicit behavior.

It remembers networks without assuming an SSID is unique.

It tolerates password changes.

It preserves old credentials instead of destroying historical information.

It prefers recently successful networks without needing a complex scoring model.

Most importantly, failure is safe:

Wi-Fi unavailable
Wi-Fi password changed
same-name network encountered
all saved credentials rejected

all lead to the same predictable result:

the always-available fallback network continues to work

The result is a small network manager built around a simple principle:

Automate repeated work, but keep network decisions explainable and recoverable.

Leave a Reply

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