Skip to main content
RunBook Academy

VyOSIV · Installation and Initial DeploymentInstallation

Post-install hardening — what to change before the box touches the public Internet

Intermediate⏱ ~22 minvyosset system loginset service sshset system syslog

What you'll learn

  • Run the post-install hardening sequence before any routed interface is brought up
  • Explain why each hardening step exists and what attack it mitigates
  • Read the audit log and SSH log to confirm the hardening took effect
  • Recognise the failure modes the hardening is meant to prevent

Prerequisites

Verified against VyOS 1.5.x LTS (circinus) · VyOS 1.4.x (sagitta) — legacy · FRRouting 10.x (VyOS 1.5) · Linux kernel 6.6 LTS (VyOS 1.5 base) · strongSwan 5.9.x (IPsec) · WireGuard 1.0.x (kernel module + userspace tooling)

Not yet marked complete on this device.

Post-install hardening — what to change before the box touches the public Internet

A freshly installed VyOS box is, by design, as boring as possible. That is also its weakness: it is boring in the wrong places, and the wrong places include the default password, the default SSH configuration, and the default firewall posture. This lesson walks the hardening sequence the routing engineer runs on every fresh box before it touches a routed interface. The sequence is reproducible, scriptable, and the same for every box in the estate.

Why this is a separate lesson

Hardening is not a one-off task. Every box needs it, every time. The sequence below is the one the engineer runs after every install and after every fresh-image bring-up. If the engineer takes a shortcut on a single box, that box becomes the weakest link in the estate and the entry point for every other box.

flowchart LR
  A[Fresh install] --> B[Change passwords]
  B --> C[SSH key-only]
  C --> D[Bind SSH to mgmt VRF]
  D --> E[Login banner]
  E --> F[NTP + time-zone]
  F --> G[Syslog + SNMP]
  G --> H[Firewall baseline]
  H --> I[SSH dynamic-protection]
  I --> J[Audit log review]
  J --> K[Box touches routed interfaces]

Step 1 — change the default password

set system login user vyos authentication plaintext-password 'correct-horse-battery-staple-92'
commit

VyOS does not prompt for the password interactively — it is an argument on the set line. Two consequences follow, and both matter:

  • The password is visible in the CLI history and in compare output for as long as it sits in the candidate configuration.
  • On commit, VyOS hashes it and replaces the plaintext-password node with an encrypted-password node. After the commit, show configuration commands | match "login user" shows a $6$-prefixed hash, not the password you typed.

For a config-management pipeline that must not carry a cleartext secret, generate the hash off-box and set it directly:

set system login user vyos authentication encrypted-password '$6$rounds=656000$...'

The new password must be long enough to survive an offline brute force against that $6$ hash. A 16-character random password is the minimum; a 24-character passphrase is better.

Step 2 — SSH key-only

set system login user vyos authentication public-keys operator@laptop key 'AAAAC3NzaC1lZDI1NTE5AAAAIExampleKeyMaterialGoesHere'
set system login user vyos authentication public-keys operator@laptop type 'ssh-ed25519'
set service ssh disable-password-authentication
set service ssh port '2222'
set service ssh listen-address '10.99.0.1'
set service ssh vrf 'mgmt'

The key value is the base64 blob only. It is not the whole line out of authorized_keys: the algorithm goes in the separate type node and the comment is the identifier you chose (operator@laptop). Pasting the full ssh-ed25519 AAAA... user@host line into key is the single most common failure in this step, and it fails at commit rather than silently.

VyOS will build the three commands for you from a key file, which removes the transcription risk entirely:

generate public-key-command user vyos path /tmp/id_ed25519.pub

disable-password-authentication sets PasswordAuthentication no in the generated sshd_config, so the daemon rejects a password attempt regardless of whether the password is correct. Note the scope: this closes the SSH password path only. The console and every other PAM consumer still accept the account password, which is exactly why step 1 comes first.

listen-address binds the daemon to the management address, and vrf 'mgmt' runs it inside the management VRF so a routed interface in the default VRF cannot reach it at all. The non-standard port reduces scanner noise; it is not a security control on its own.

Step 3 — login banner

set system login banner pre-login "AUTHORIZED ACCESS ONLY. All activity is monitored and logged."

The banner is the legal hook for any later forensic or legal action. Without a banner, some jurisdictions cannot prosecute unauthorised access even when the access is logged. post-login takes a second banner shown after authentication succeeds.

Step 4 — NTP and time-zone

VyOS 1.4 moved NTP out of system and into service, and swapped ntpd for chrony. The 1.5 form is:

set system time-zone 'Europe/London'
delete service ntp server time1.vyos.net
delete service ntp server time2.vyos.net
delete service ntp server time3.vyos.net
set service ntp server 'ntp1.internal.example.com'
set service ntp server 'ntp2.internal.example.com' prefer
set service ntp vrf 'mgmt'

Correct time matters for log correlation, certificate validation, BGP session timers, and every other timestamped event on the box. The three *.vyos.net servers ship in the default configuration; production should point at an internal source, both for accuracy and to stop the box making outbound connections to a vendor.

Step 5 — syslog and SNMP export

The syslog tree was restructured in 1.4: the remote collector node is remote, not host, and global became local.

set system syslog remote 10.99.0.50 facility all level 'info'
set system syslog remote 10.99.0.50 protocol 'udp'
set system syslog remote 10.99.0.50 port '514'
set system syslog remote 10.99.0.50 vrf 'mgmt'
set service snmp community 'ro-monitoring' authorization 'ro'
set service snmp community 'ro-monitoring' network '10.99.0.0/24'
set service snmp location 'DC1 row 4 rack 7'
set service snmp contact 'noc@example.com'
set service snmp listen-address '10.99.0.1'

SNMP has always lived under service, never system. Two things carry the hardening here: community ... network restricts which sources may poll, and listen-address stops the agent binding to every address on the box.

Step 6 — firewall baseline

The default posture is not “allow all” because of a permissive rule; it is “allow all” because there are no chains at all. The hardening creates them. VyOS 1.4 removed the per-interface firewall in / out / local bindings and replaced them with three base chains — input (traffic to the router), forward (traffic through it) and output — each of which can jump to a named chain.

Start with the state policy, which applies to every base chain at once:

set firewall global-options state-policy established action 'accept'
set firewall global-options state-policy related action 'accept'
set firewall global-options state-policy invalid action 'drop'

Then the named chain for traffic arriving from the WAN:

set firewall ipv4 name WAN-IN default-action 'drop'
set firewall ipv4 name WAN-IN default-log
set firewall ipv4 name WAN-IN description 'Internet to LAN'
set firewall ipv4 name WAN-IN rule 10 action 'accept'
set firewall ipv4 name WAN-IN rule 10 description 'return traffic for LAN-initiated flows'
set firewall ipv4 name WAN-IN rule 10 state 'established'
set firewall ipv4 name WAN-IN rule 20 action 'accept'
set firewall ipv4 name WAN-IN rule 20 state 'related'

The chain does nothing until a base chain jumps to it:

set firewall ipv4 forward filter default-action 'drop'
set firewall ipv4 forward filter rule 100 action 'jump'
set firewall ipv4 forward filter rule 100 jump-target 'WAN-IN'
set firewall ipv4 forward filter rule 100 inbound-interface name 'eth0'

action jump without jump-target is rejected at commit, and a jump-target that names a chain you have not created is rejected too — which is a mercy, because the 1.3 per-interface binding failed the same mistake silently.

Protecting the router itself is a separate chain, and it is the one people forget:

set firewall ipv4 input filter default-action 'drop'
set firewall ipv4 input filter rule 10 action 'accept'
set firewall ipv4 input filter rule 10 description 'SSH from management only'
set firewall ipv4 input filter rule 10 destination port '2222'
set firewall ipv4 input filter rule 10 protocol 'tcp'
set firewall ipv4 input filter rule 10 source address '10.99.0.0/24'
set firewall ipv4 input filter rule 20 action 'accept'
set firewall ipv4 input filter rule 20 protocol 'icmp'

The breakfix lessons in Part XLVII cover the firewall failure modes in detail.

Step 7 — SSH brute-force protection

VyOS does not expose a PAM account-lockout knob in the CLI. There is no system login retry and no system login lockout node; the only rate-limiting under system login applies to OTP verification, not to password attempts. If you need pam_faillock semantics you are configuring it outside the VyOS configuration tree, and it will not survive an image upgrade.

What VyOS does ship is sshd-level protection driven by sshguard, which watches the auth log and installs a temporary block for a source address that keeps failing:

set service ssh dynamic-protection threshold '30'
set service ssh dynamic-protection block-time '120'
set service ssh dynamic-protection detect-time '1800'
set service ssh dynamic-protection allow-from '10.99.0.0/24'

threshold is a score, not an attempt count — sshguard weights different failure types differently — so tune it against show log ssh dynamic-protection rather than assuming “30 means thirty tries”. allow-from is the allow-list that keeps your own management range from ever being blocked; set it before you deploy this, not after you have locked yourself out.

Combined with disable-password-authentication from step 2, the brute-force surface is already small: an attacker cannot guess their way in with a password at all. dynamic-protection is there to stop the log noise and the CPU cost of the attempt.

Step 8 — audit the result

show configuration commands | match "system login"
show configuration commands | match "service ssh"
show configuration commands | match "service ntp"
show configuration commands | match "firewall ipv4"

The engineer reads each output and confirms the change took effect. Then read the auth log, which is where the hardening either shows up or does not:

Read-only / Safe
$ show log authorization | match sshd
sshd[1234]: Accepted publickey for vyos from 10.99.0.99 port 54321 ssh2: ED25519 SHA256:...
sshd[1240]: Connection closed by authenticating user vyos 203.0.113.7 port 41022 [preauth]

Illustrative output

Two things to read here. The first line is what a hardened box looks like: Accepted publickey, never Accepted password. The second is what disable-password-authentication produces when someone tries a password — the connection is closed at preauth because the daemon offered no password method to attempt.

How the result is validated

show configuration commands | match "login user vyos"
show firewall ipv4 name WAN-IN
show firewall ipv4 input filter
show service snmp
show ssh dynamic-protection

The firewall has rules and the input chain protects the router; the SSH daemon is on the management VRF; the user has a public-key entry and an encrypted-password — never a plaintext-password — in the running configuration.

How it fails

The production failure modes the engineer must recognise:

  • Hardening script run against a fresh image, not against the cloud image. The cloud image has the same defaults; the hardening script must run on it too.
  • SSH key bootstrap in the wrong order. An operator who pastes the key and sets disable-password-authentication before changing the password has closed one door and left the console wide open with vyos:vyos. Always change the password first.
  • Full authorized_keys line pasted into key. The commit fails, the operator retries with the same paste, and the box ends up with disable-password-authentication set and no working key. Use generate public-key-command.
  • input filter default-action drop committed before the SSH accept rule. The session dies mid-commit. Recovery is console only.
  • Banner not set. The operator discovers in a post-incident review that the banner was missing and the legal team cannot use the access logs as evidence.
  • SNMP on a routed interface. With no vrf node available for the agent, an operator who skips listen-address and the input chain exposes the routing table and the interface list to anyone who guesses the community string.
  • Hardening applied but not saved. A reboot reverts to the unhardened state. Always commit; save.
  • Hardening script run with no dry-run. A typo in the script silently removes the operator’s SSH key. The next reboot locks the operator out. Always test the script in a lab first.

Rollback

The hardening sequence is additive — it does not replace the default configuration; it augments it. There is no rollback needed. The recovery path for a box that has been over-hardened:

  1. Connect via console / IPMI.
  2. Identify the offending change with compare in configure mode.
  3. delete the bad entry, commit; save.

For a box where the operator’s SSH key was wiped:

  1. Console login with the vyos account and the (still valid) password — this is the reason step 1 sets a real password rather than deleting password authentication outright.
  2. Re-add the public key.
  3. Commit, save.

Production discipline

Cross-course references

The OPNsense course’s I-OPNsense-Hardening covers the equivalent sequence on the firewall side. The Linux course’s V-Linux-NetConfig covers the underlying PAM and SSH configuration. The Observability course’s XII-Observability-HostAgents covers the syslog and SNMP export configuration in detail.

Quiz

Knowledge check · 4 questions

  1. Q1. What is the first hardening action on a freshly installed VyOS box?

  2. Q2. `set service ssh disable-password-authentication` closes the SSH password path only — the console and every other PAM consumer still accept the account password.

  3. Q3. A fresh VyOS box has been hardened and is in production. The operator cannot ssh with the correct key. What is the most likely cause?

    The hardening script pasted the operator's public key, set `disable-password-authentication`, set `listen-address 10.99.0.1` and `vrf 'mgmt'` on the SSH service, and committed. The operator's laptop is on the 10.99.0.0/24 network but is reaching the box via 10.0.0.1 on `eth0`.

  4. Q4. A box was hardened but after a reboot the operator finds the default `vyos:vyos` password is back. What happened?

    The operator ran the hardening sequence, typed `commit`, but skipped `save`. The reboot reverted to the previous saved configuration.

Passing score: 75%. Answers are checked in this browser.