SSH: sshd hardening on FreeBSD and Linux, and 1Password integration
0 (0)

By | 12/29/2025
Click to rate this post!
[Total: 0 Average: 0]

It is time to tidy up SSH on FreeBSD itself and on the clients – laptops running Arch Linux, as I am still using password authentication on my home machines.

Actually, the settings described below are specific neither to FreeBSD nor to Linux, as the SSH server is the same on all systems (OpenSSH_9.9p2 on FreeBSD 14.3 and OpenSSH_10.2p1 on Arch Linux).

I have been using 1Password for a long time; it has great integration and a GUI, although I also maintain a local KeePassXC in parallel, where I periodically back up data from 1Password into a separate database – see How to export your data from the 1Password desktop app and Migrating from 1Password to KeePass, KeePassXC and KeePassium.

This post is part of a series on setting up a home NAS on FreeBSD (see the beginning at FreeBSD: Home NAS, part 1 – setting up ZFS mirror), but I decided to make it a separate topic since it is exclusively about SSH.

What I have in the current setup:

SSH and Key-Based Authentication

The first and most important step is to set up key-based access instead of password authentication.

We will do this first, then disable password access entirely.

On the client, a Linux laptop, generate the keys:

[setevoy@setevoy-work ~] $ ssh-keygen -t ed25519 -C "setevoy@setevoy"
Generating public/private ed25519 key pair.
Enter file in which to save the key (/home/setevoy/.ssh/id_ed25519): /home/setevoy/.ssh/freebsd-nas
Enter passphrase for "/home/setevoy/.ssh/freebsd-nas" (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /home/setevoy/.ssh/freebsd-nas
Your public key has been saved in /home/setevoy/.ssh/freebsd-nas.pub

Copy the key to the FreeBSD host:

[setevoy@setevoy-work ~] $ ssh-copy-id -i /home/setevoy/.ssh/freebsd-nas [email protected]
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/setevoy/.ssh/freebsd-nas.pub"
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
([email protected]) Password for setevoy@setevoy-nas:

Number of key(s) added: 1

Now try logging into the machine, with: "ssh -i /home/setevoy/.ssh/freebsd-nas '[email protected]'"
and check to make sure that only the key(s) you wanted were added.

Check the ~/.ssh/authorized_keys file on FreeBSD for the user setevoy:

root@setevoy-nas:/home/setevoy # cat .ssh/authorized_keys 
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILb2zkJzflngGx0qY71xYyHVvKI8A2GTAGTqppS0yVz2 setevoy@setevoy

Try connecting from Linux:

[setevoy@setevoy-work ~]  $ ssh -i /home/setevoy/.ssh/freebsd-nas '[email protected]'
Last login: Sat Dec 27 09:01:47 2025 from 192.168.0.4
FreeBSD 14.3-RELEASE (GENERIC) releng/14.3-n271432-8c9ce319fef7

Welcome to FreeBSD!

...
[setevoy@setevoy-nas ~]$ 

Everything works – now we can configure the SSH Agent.

FreeBSD and 1Password Client

Just as an example – installing and connecting the 1Password client on FreeBSD.

Install it:

root@setevoy-nas:/home/setevoy # pkg install -y 1password-client2

Add an account:

root@setevoy-nas:/home/setevoy # op account add
Enter your sign-in address (example.1password.com): my.1password.com            
Enter the email address for your account on my.1password.com: <ACCOUNT_EMAIL>
Enter the Secret Key for [email protected] on my.1password.com: <SECRET_KEY>
Enter the password for [email protected] at my.1password.com: 
Enter your six-digit authentication code: <OTP_CODE>
Now run 'eval $(op signin)' to sign in.

Check accounts:

root@setevoy-nas:/home/setevoy # op account list
SHORTHAND    URL                         EMAIL            USER ID
my           https://my.1password.com    [email protected]     7BS***KMM

Sign in:

root@setevoy-nas:/home/setevoy # eval $(op signin)
Enter the password for [email protected] at my.1password.com:

And now we have access to secrets.

For example, to retrieve the key that we will add later:

root@setevoy-nas:/home/setevoy # op item get "FreeBSD NAS SSH"
ID:          ulz***4ce
Title:       FreeBSD NAS SSH
Vault:       Personal (wb7***guq)
Created:     1 week ago
Updated:     1 week ago by Arseny
Favorite:    false
Tags:        FreeBSD,SSH
Version:     1
Category:    LOGIN
Fields:
  password:    [use 'op item get ulz***4ce --reveal' to reveal]
  username:    setevoy

Linux, 1Password, and SSH Agent

I wrote in more detail about this in a 2019 post, SSH: RSA keys and ssh-agent – managing SSH keys and their passwords, but since I now actively use 1Password, which can both store the keys themselves and act as an SSH Agent.

Documentation – 1Password SSH agent and Get started with 1Password for SSH.

Retrieve the private key:

[setevoy@setevoy-work ~] $ cat .ssh/freebsd-nas
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
...
vKI8A2GTAGTqppS0yVz2AAAAD3NldGV2b3lAc2V0ZXZveQECAwQFBg==
-----END OPENSSH PRIVATE KEY-----

Copy and add a new key to 1Password (although it can generate keys itself):

Go to Settings:

Go to Developer – Set up the SSH Agent:

1Password will show the contents of the ~/.ssh/config file and even offer to update it:

However, my current config already has some settings:

# GitHub.com
Host github.com
  PreferredAuthentications publickey
  IdentityFile /home/setevoy/.ssh/setevoy_main_priv_openssh

ExitOnForwardFailure yes

So, add it to the end of the file manually.

Specify two hosts – nas.setevoy and, in case Unbound is unavailable, the IP address of the FreeBSD host:

...

Host nas.setevoy 192.168.0.2
  IdentityAgent ~/.1password/agent.sock

Run ssh nas.setevoy; 1Password will request confirmation – your 1Password master password:

And now everything works through its SSH Agent:

[setevoy@setevoy-work ~] $ ssh nas.setevoy
...
Last login: Sat Dec 27 09:03:15 2025 from 192.168.0.4
FreeBSD 14.3-RELEASE (GENERIC) releng/14.3-n271432-8c9ce319fef7

Welcome to FreeBSD!

...
[setevoy@setevoy-nas ~]$ 

How the SSH Agent Works

When connecting, the SSH client:

  • reads the configuration (~/.ssh/config, CLI options)
  • connects to the SSH agent (in our case, 1Password) and receives a list of available public keys
  • sequentially offers the server the public keys it has (from the agent and, unless restricted, from ~/.ssh/)
  • sshd on the server looks into ~/.ssh/authorized_keys of the specific user and checks if the offered public key is there
  • when the server finds a match, it accepts the public key and sends the client data to sign (a random set of numbers)
  • the client passes this data to the SSH agent, which possesses the corresponding private key
  • the agent creates a cryptographic signature with the private key and returns it to the client
  • the server verifies the signature with the public key and completes the authentication

We can observe this with ssh -v user@host:

[setevoy@setevoy-work ~] $ ssh -v [email protected]
debug1: OpenSSH_10.2p1, OpenSSL 3.6.0 1 Oct 2025
debug1: Reading configuration data /home/setevoy/.ssh/config
...
debug1: Connecting to nas.setevoy [192.168.0.2] port 22.
debug1: Connection established.
...
debug1: Authenticating to nas.setevoy:22 as 'setevoy'
...
debug1: Will attempt key: RTFM RSA SHA256:nedb3Qgpkxgu57MRP7/eXShHgw6N6b7SjZ3S1rNyFb4 agent
debug1: Will attempt key: FreeBSD-NAS ED25519 SHA256:ZuJ77z6BNMwra41BiTKDrJSSQrDJ0/u+4wZRCvGJMpA agent
debug1: Will attempt key: /home/setevoy/.ssh/id_rsa 
debug1: Will attempt key: /home/setevoy/.ssh/id_ecdsa 
debug1: Will attempt key: /home/setevoy/.ssh/id_ecdsa_sk 
debug1: Will attempt key: /home/setevoy/.ssh/id_ed25519 ED25519 SHA256:X8L1lCBQz8Bk7K5rMGqiE+tlSthCbgaqK7ryLZ6gVWU
debug1: Will attempt key: /home/setevoy/.ssh/id_ed25519_sk 
debug1: Offering public key: RTFM RSA SHA256:nedb3Qgpkxgu57MRP7/eXShHgw6N6b7SjZ3S1rNyFb4 agent
debug1: Authentications that can continue: publickey
debug1: Offering public key: FreeBSD-NAS ED25519 SHA256:ZuJ77z6BNMwra41BiTKDrJSSQrDJ0/u+4wZRCvGJMpA agent
debug1: Server accepts key: FreeBSD-NAS ED25519 SHA256:ZuJ77z6BNMwra41BiTKDrJSSQrDJ0/u+4wZRCvGJMpA agent
Authenticated to nas.setevoy ([192.168.0.2]:22) using "publickey".
...
Last login: Sun Dec 28 13:44:51 2025 from 192.168.0.4
FreeBSD 14.3-RELEASE (GENERIC) releng/14.3-n271432-8c9ce319fef7

Welcome to FreeBSD!
...

[setevoy@setevoy-nas ~]$

And for the keys “Will attempt key: RTFM RSA [...] and FreeBSD-NAS [...], we can clearly see they were obtained from the agent.

SSH Agent and the “Too many authentication failures” Error

As seen above, a whole batch of keys is transmitted, and if there are many, the server might reject further authentication.

The number of attempts is set on the server by the MaxAuthTries parameter (default “6”) in /etc/ssh/sshd_config. Thus, if we have 7 keys and the first 6 don’t match – we won’t be able to connect.

To tell the SSH client exactly which private key from the agent to use – specify the public key and set IdentitiesOnly:

...

Host nas.setevoy 192.168.0.2
  IdentityAgent ~/.1password/agent.sock
  IdentityFile ~/.ssh/freebsd-nas.pub
  IdentitiesOnly yes

And now during connection, the client will only transmit this key:

[setevoy@setevoy-work ~] $ ssh -v [email protected]
...
debug1: get_agent_identities: agent returned 2 keys
debug1: Will attempt key: /home/setevoy/.ssh/freebsd-nas.pub ED25519 SHA256:ZuJ77z6BNMwra41BiTKDrJSSQrDJ0/u+4wZRCvGJMpA explicit agent
debug1: Offering public key: /home/setevoy/.ssh/freebsd-nas.pub ED25519 SHA256:ZuJ77z6BNMwra41BiTKDrJSSQrDJ0/u+4wZRCvGJMpA explicit agent
debug1: Server accepts key: /home/setevoy/.ssh/freebsd-nas.pub ED25519 SHA256:ZuJ77z6BNMwra41BiTKDrJSSQrDJ0/u+4wZRCvGJMpA explicit agent
Authenticated to nas.setevoy ([192.168.0.2]:22) using "publickey".
...

Last login: Sun Dec 28 13:44:57 2025 from 192.168.0.4
FreeBSD 14.3-RELEASE (GENERIC) releng/14.3-n271432-8c9ce319fef7

Welcome to FreeBSD!
...

[setevoy@setevoy-nas ~]$

Basic SSH Server Hardening

See sshd_config.

You can verify the current configuration with sshd -T:

root@setevoy-nas:/home/setevoy # sshd -T 
port 22
addressfamily any
listenaddress 0.0.0.0:22
...

For example, whether root login is allowed:

root@setevoy-nas:/home/setevoy # sshd -T | grep root
permitrootlogin no

Edit the /etc/ssh/sshd_config config, set the minimal parameters, and let PermitRootLogin no be explicitly stated here.

Plus, explicitly grant permission for PubkeyAuthentication and PasswordAuthentication – we will disable password authentication later once we are certain everything works:

PermitRootLogin no
PubkeyAuthentication yes
PasswordAuthentication yes

Verify syntax with sshd -t; if all is well, the command will output nothing:

root@setevoy-nas:/home/setevoy # sshd -t; echo $?
0

Perform a config reload:

root@setevoy-nas:/home/setevoy # service sshd reload
Performing sanity check on sshd configuration.

Check if access from Linux works:

[setevoy@setevoy-work ~]  $ ssh nas.setevoy
...
[setevoy@setevoy-nas ~]$

pf is already configured, see FreeBSD: Home NAS, part 2 – introduction to Packet Filter (PF) firewall:

...

# allow SSH from Office LAN (192.168.0.0/24) to FreeBSD host
pass in log on em0 proto tcp from 192.168.0.0/24 to (em0) port 22 keep state

# allow SSH from Home network (192.168.100.0/24) to FreeBSD host
pass in log on em0 proto tcp from 192.168.100.0/24 to (em0) port 22 keep state

# allow SSH from VPN clients to FreeBSD host
pass in on wg0 proto tcp from 10.8.0.0/24 to (wg0) port 22 keep state
...

Networks can be specified as { 192.168.0.0/24, 192.168.100.0/24, 10.8.0.0/24 }, but for SSH I decided to leave it in this form to be explicitly clear.

Furthermore, a few more parameters can be added:

  • PermitRootLogin no: prohibit root connection, already done
  • PubkeyAuthentication yes: key-based authentication, also already done
  • PasswordAuthentication yes: password authentication, leave enabled for now
  • ChallengeResponseAuthentication no: disable keyboard-interactive authentication via PAM, which is not needed without 2FA
    • an additional nuance here is that even if PasswordAuthentication is disabled, if ChallengeResponseAuthentication is “yes” and UsePAM is “yes” – the system might still request password authentication
  • UsePAM yes: leave enabled for logging, session accounting, and access policies (see Practical Effects of Setting “UsePAM yes” on SSH in Linux)
  • AllowUsers setevoy or AllowGroups wheel: which users or groups can connect via SSH
  • X11Forwarding no: block X11 forwarding – there is no graphical server here anyway
  • AllowAgentForwarding no: prohibit the use of client’s local SSH keys on the server through the client’s SSH agent
  • AllowTcpForwarding no: prohibit SSH tunnels – definitely not needed on a home NAS (see SSH Tunnels: Secure Remote Access and Port Forwarding)
  • AuthorizedKeysFile .ssh/authorized_keys: this is the default value, but set it explicitly

Check once more with sshd -t, perform a reload, and verify the connection with the key from the client.

If all is OK – add a bit more tuning:

  • PasswordAuthentication no: now disable password authentication
  • AuthenticationMethods publickey: explicitly prohibit all mechanisms except keys
  • MaxSessions 2: maximum number of active sessions per TCP connection
  • LoginGraceTime 30: number of seconds for a client to complete authentication

For even greater security – you can set up a different port instead of 22 (e.g., set Port 2222), limit the IPs sshd listens on (parameter ListenAddress 192.168.0.2), and even configure Two-factor Authentication For SSH – but for a home server accessible only from local networks, this is already overkill.

Loading