VyOSIV · Installation and Initial DeploymentInstallation
Post-install hardening — what to change before the box touches the public Internet
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)
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
compareoutput for as long as it sits in the candidate configuration. - On
commit, VyOS hashes it and replaces theplaintext-passwordnode with anencrypted-passwordnode. 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:
$ show log authorization | match sshdsshd[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-authenticationbefore changing the password has closed one door and left the console wide open withvyos:vyos. Always change the password first. - Full
authorized_keysline pasted intokey. The commit fails, the operator retries with the same paste, and the box ends up withdisable-password-authenticationset and no working key. Usegenerate public-key-command. input filter default-action dropcommitted 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
vrfnode available for the agent, an operator who skipslisten-addressand 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:
- Connect via console / IPMI.
- Identify the offending change with
comparein configure mode. deletethe bad entry,commit; save.
For a box where the operator’s SSH key was wiped:
- Console login with the
vyosaccount and the (still valid) password — this is the reason step 1 sets a real password rather than deleting password authentication outright. - Re-add the public key.
- 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
Q1. What is the first hardening action on a freshly installed VyOS box?
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.
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`.
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.