[GH-ISSUE #5288] Non-atomic change of 99-netbird.conf can result in SSH connection errors #11104

Open
opened 2026-08-05 01:28:29 -04:00 by saavagebueno · 0 comments
Owner

Originally created by @pitkley on GitHub (Feb 10, 2026).
Original GitHub issue: https://github.com/netbirdio/netbird/issues/5288

Describe the problem

Executing an SSH command precisely at a moment when the Netbird client is writing the /etc/ssh/ssh_config.d/99-netbird.conf configuration can result in that SSH command failing with errors such as the following:

/etc/ssh/ssh_config.d/99-netbird.conf: line 12: Bad configuration option: pubkeyauthenti
/etc/ssh/ssh_config.d/99-netbird.conf: terminating, 1 bad configuration options

The cut off pubkeyauthenti option indicates that the SSH command read this config file while it was in the process of being written.

While not a problem usually, in environments where (especially ephemeral) instances are joining and leaving the network frequently, changes to 99-netbird.conf are frequent (assuming the peers are accessible by policy), and then SSH command failures become more likely.

(In my case I especially notice this when executing Ansible playbooks that have many hosts, where the chance of hitting the exact moment the configuration changes is higher.)

To Reproduce

It is not super trivial to reproduce the issue, but in essence:

  1. Configure a policy that allows your client device to SSH into a group.
    (I'm not (yet) making use of Netbird's native SSH functionality, so in my case this is just a 22/tcp allow.)
  2. Have many peers continuously join and leave the group from the policy of step 1.
  3. Continuously execute SSH commands on your client device.
    If you hit an inopportune moment, the SSH command will fail with a configuration error similar to the one shared above.

Expected behavior

Changes to 99-netbird.conf should not break SSH commands that execute at the same time.

Are you using NetBird Cloud?

No, self-hosted (observed with server version 0.64.0 through 0.64.5, dashboard 2.27.2/2.28.0/2.31.0).

NetBird version

Observed on client versions 0.64.0 through 0.64.5.

Is any other VPN software installed?

No.

Additional context

I was running a modified version of the client for about a week, switching the write of the config to happen atomically:

diff --git a/client/ssh/config/manager.go b/client/ssh/config/manager.go
index cc47fd2d..f63d26fd 100644
--- a/client/ssh/config/manager.go
+++ b/client/ssh/config/manager.go
@@ -1,6 +1,7 @@
 package config
 
 import (
+	"bytes"
 	"context"
 	"fmt"
 	"os"
@@ -13,6 +14,8 @@ import (
 	log "github.com/sirupsen/logrus"
 
 	nbssh "github.com/netbirdio/netbird/client/ssh"
+
+	"codeberg.org/sdassow/atomic"
 )
 
 const (
@@ -71,7 +74,8 @@ func writeFileWithTimeout(filename string, data []byte, perm os.FileMode) error
 
 	done := make(chan error, 1)
 	go func() {
-		done <- os.WriteFile(filename, data, perm)
+		reader := bytes.NewReader(data)
+		done <- atomic.WriteFile(filename, reader, atomic.FileMode(perm))
 	}()
 
 	select {

With this patch in place I had not seen the described behavior once. I then switched back to the official client where I then encountered the issue again.

I decided to open this issue instead of opening a PR because I'm not sure how you feel about bringing in a new dependency for the atomic file-writes and thus wanted to seek the discussion first.

Have you tried these troubleshooting steps?

  • Reviewed client troubleshooting (if applicable)
  • Checked for newer NetBird versions
  • Searched for similar issues on GitHub (including closed ones)
  • Restarted the NetBird client
  • Disabled other VPN software
  • Checked firewall settings
Originally created by @pitkley on GitHub (Feb 10, 2026). Original GitHub issue: https://github.com/netbirdio/netbird/issues/5288 **Describe the problem** Executing an SSH command precisely at a moment when the Netbird client is writing the `/etc/ssh/ssh_config.d/99-netbird.conf` configuration can result in that SSH command failing with errors such as the following: ``` /etc/ssh/ssh_config.d/99-netbird.conf: line 12: Bad configuration option: pubkeyauthenti /etc/ssh/ssh_config.d/99-netbird.conf: terminating, 1 bad configuration options ``` The cut off `pubkeyauthenti` option indicates that the SSH command read this config file while it was in the process of being written. While not a problem usually, in environments where (especially ephemeral) instances are joining and leaving the network frequently, changes to `99-netbird.conf` are frequent (assuming the peers are accessible by policy), and then SSH command failures become more likely. (In my case I especially notice this when executing Ansible playbooks that have many hosts, where the chance of hitting the exact moment the configuration changes is higher.) **To Reproduce** It is not super trivial to reproduce the issue, but in essence: 1. Configure a policy that allows your client device to SSH into a group. (I'm not (yet) making use of Netbird's native SSH functionality, so in my case this is just a 22/tcp allow.) 2. Have many peers continuously join and leave the group from the policy of step 1. 3. Continuously execute SSH commands on your client device. If you hit an inopportune moment, the SSH command will fail with a configuration error similar to the one shared above. **Expected behavior** Changes to `99-netbird.conf` should not break SSH commands that execute at the same time. **Are you using NetBird Cloud?** No, self-hosted (observed with server version 0.64.0 through 0.64.5, dashboard 2.27.2/2.28.0/2.31.0). **NetBird version** Observed on client versions 0.64.0 through 0.64.5. **Is any other VPN software installed?** No. **Additional context** I was running a modified version of the client for about a week, switching the write of the config to happen atomically: ```patch diff --git a/client/ssh/config/manager.go b/client/ssh/config/manager.go index cc47fd2d..f63d26fd 100644 --- a/client/ssh/config/manager.go +++ b/client/ssh/config/manager.go @@ -1,6 +1,7 @@ package config import ( + "bytes" "context" "fmt" "os" @@ -13,6 +14,8 @@ import ( log "github.com/sirupsen/logrus" nbssh "github.com/netbirdio/netbird/client/ssh" + + "codeberg.org/sdassow/atomic" ) const ( @@ -71,7 +74,8 @@ func writeFileWithTimeout(filename string, data []byte, perm os.FileMode) error done := make(chan error, 1) go func() { - done <- os.WriteFile(filename, data, perm) + reader := bytes.NewReader(data) + done <- atomic.WriteFile(filename, reader, atomic.FileMode(perm)) }() select { ``` With this patch in place I had not seen the described behavior once. I then switched back to the official client where I then encountered the issue again. I decided to open this issue instead of opening a PR because I'm not sure how you feel about bringing in a new dependency for the atomic file-writes and thus wanted to seek the discussion first. **Have you tried these troubleshooting steps?** - [x] Reviewed [client troubleshooting](https://docs.netbird.io/how-to/troubleshooting-client) (if applicable) - [x] Checked for newer NetBird versions - [x] Searched for similar issues on GitHub (including closed ones) - [x] Restarted the NetBird client - [x] Disabled other VPN software - [x] Checked firewall settings
saavagebueno added the triage-needed label 2026-08-05 01:28:29 -04:00
Sign in to join this conversation.
No Label triage-needed
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: DYNR/netbird#11104