I installed WireGuard on an Ubuntu 26.04 EC2 instance and connected to the instance through an AWS SSM Session Manager session.
After installing WireGuard, I first noticed the issue when I ran:
wg --versionThe command appeared to print nothing.
At first, I thought this was only a wg --version problem. However, after debugging it further, the real issue was broader.
The WireGuard wg command had I/O issues inside the SSM Session Manager session. Commands that depended on inherited stdin, stdout, stderr, or redirected files outside the paths allowed by the wg AppArmor profile could fail.
The same command worked correctly on my local Ubuntu 26.04 VM.
The real cause was AppArmor.
More specifically, the issue was caused by the interaction between:
AWS SSM Session Manager
Snap-installed amazon-ssm-agent
AppArmor confinement
Ubuntu 26.04's /usr/bin/wg AppArmor profileEnvironment
In this example, I used:
Cloud: AWS EC2
OS: Ubuntu 26.04
Login method: AWS SSM Session Manager
Package: wireguard-tools
Command: wgThe installed WireGuard userspace tool was:
wireguard-tools v1.0.20250521The First Symptom
Inside the SSM Session Manager session, this command appeared to print nothing:
wg --versionRedirecting stdout and stderr to files also produced empty files:
wg --version > /tmp/wg.stdout 2> /tmp/wg.stderr
wc -c /tmp/wg.stdout /tmp/wg.stderrOutput:
0 /tmp/wg.stdout
0 /tmp/wg.stderr
0 totalHowever, this worked:
wg --version 2>&1 | catOutput:
wireguard-tools v1.0.20250521 - https://git.zx2c4.com/wireguard-tools/So wg was not completely broken. The command could still print output in some situations.
That meant the issue was probably related to file descriptors, redirection, or the execution environment.
Confirm the Real wg Binary
First, I checked whether wg was really /usr/bin/wg:
command -v wg
readlink -f "$(command -v wg)"
ls -l "$(command -v wg)"
file "$(command -v wg)"
dpkg -S "$(command -v wg)"Example output:
/usr/bin/wg
/usr/bin/wg
-rwxr-xr-x 1 root root 134344 Nov 20 15:00 /usr/bin/wg
/usr/bin/wg: ELF 64-bit LSB pie executable, ARM aarch64
wireguard-tools: /usr/bin/wgThis confirmed that wg was the real binary from the wireguard-tools package.
It was not an alias, shell function, or wrapper script.
Debug with strace
Next, I used strace to see whether wg actually tried to write output.
strace -f -e trace=write,openat,exit_group \
-o /tmp/trace_direct.txt \
/usr/bin/wg --version > /tmp/test_direct.stdoutThen I checked the trace file:
cat /tmp/trace_direct.txtThe important line was:
write(1, "wireguard-tools v1.0.20250521 - "..., 71) = -1 EBADF (Bad file descriptor)This means wg did try to write to file descriptor 1.
File descriptor 1 is stdout.
But stdout was invalid inside the wg process.
So the command was not silent because WireGuard did not print anything. It was silent because stdout was already broken when wg tried to write to it.
When I used a pipe, it worked:
strace -f -e trace=write,openat,exit_group \
-o /tmp/trace_pipe.txt \
/usr/bin/wg --version 2>&1 | catThe trace showed:
write(1, "wireguard-tools v1.0.20250521 - "..., 71) = 71So the difference was:
Direct stdout/file redirection: write(1) = EBADF
Pipe: write(1) = 71This was the first important clue.
Normal Shell Redirection Still Worked
To make sure SSM Session Manager was not breaking all shell redirection, I tested normal commands:
/bin/echo "hello echo" > /tmp/echo.stdout
/usr/bin/printf "hello printf\n" > /tmp/printf.stdout
/bin/date > /tmp/date.stdout
wc -c /tmp/echo.stdout /tmp/printf.stdout /tmp/date.stdout
cat -A /tmp/echo.stdout
cat -A /tmp/printf.stdout
cat -A /tmp/date.stdoutThese commands worked normally.
So the issue was not /tmp, not the shell, and not redirection in general.
The issue was specific to /usr/bin/wg.
Copying wg to /tmp Worked
Then I copied the same binary to /tmp:
cp /usr/bin/wg /tmp/wg.copy
chmod 755 /tmp/wg.copy
/tmp/wg.copy --version > /tmp/wg.copy.out 2> /tmp/wg.copy.err
wc -c /tmp/wg.copy.out /tmp/wg.copy.err
cat -A /tmp/wg.copy.out
cat -A /tmp/wg.copy.errOutput:
71 /tmp/wg.copy.out
0 /tmp/wg.copy.err
71 total
wireguard-tools v1.0.20250521 - https://git.zx2c4.com/wireguard-tools/This was a major clue.
The same executable content worked when copied to another path.
That strongly suggested a path-based security policy.
The Root Cause: AppArmor
I checked the current AppArmor confinement context:
cat /proc/$$/attr/currentOutput:
snap.amazon-ssm-agent.amazon-ssm-agent (complain)This means the SSM shell was not a normal unconfined shell. It was started by the Snap version of amazon-ssm-agent, and it was running under an AppArmor profile.
Then I checked the AppArmor profiles:
sudo aa-status | grep -iE 'wg|wireguard|ssm|amazon|session|profile|enforce' || trueThe system had WireGuard-related AppArmor profiles loaded:
wg
wg-quick
wg-quick//ip
wg-quick//nft
wg-quick//sysctlThen I checked the wg profile:
sudo sed -n '1,220p' /etc/apparmor.d/wgThe relevant part was:
profile wg /usr/bin/wg flags=(attach_disconnected, complain) {
include <abstractions/base>
include <abstractions/nameservice-strict>
capability net_admin,
capability net_bind_service,
network netlink raw,
network inet dgram,
network inet6 dgram,
network inet stream,
network inet6 stream,
file rw @{etc_rw}/wireguard/{,**},
mr @{exec_path},
include if exists <local/wg>
}The complain flag appeared there because I had already switched the profile to complain mode during testing.
The important part is this rule:
file rw @{etc_rw}/wireguard/{,**},That allows WireGuard files under:
/etc/wireguard/But the profile did not allow inherited access to paths such as:
/tmp/*
/dev/pts/*
/dev/ttyThat matters because in an SSM Session Manager session, stdout and stderr are connected to a pseudo-terminal such as /dev/pts/0.
If I redirect output to files, the inherited stdout/stderr file descriptors may point to files under /tmp.
Kernel Logs Confirmed the Problem
The kernel logs confirmed the AppArmor denial:
sudo journalctl -k --no-pager | grep -iE 'apparmor|DENIED|wg'Important lines included:
apparmor="DENIED" operation="file_inherit" class="file" profile="wg" name="/tmp/wg.stdout" requested_mask="w" denied_mask="w"
apparmor="DENIED" operation="file_inherit" class="file" profile="wg" name="/tmp/wg.stderr" requested_mask="w" denied_mask="w"There were also denials for the pseudo-terminal:
apparmor="DENIED" operation="file_inherit" class="file" profile="wg" name="/dev/pts/0" requested_mask="wr" denied_mask="wr"The logs also showed denials for temporary key files such as:
/tmp/ec2_private.key
/tmp/ec2_public.keySo the issue was broader than wg --version.
The real issue was that the wg AppArmor profile denied inherited file descriptors when /usr/bin/wg was executed from the SSM Session Manager environment. The debug evidence also showed that changing the wg profile to complain mode allowed /usr/bin/wg --version > /tmp/wg.stdout to write 71 bytes successfully, and the profile itself allowed /etc/wireguard/**.
The flow was:
SSM Session Manager shell
→ executes /usr/bin/wg
→ AppArmor transitions the process into the wg profile
→ the wg profile denies inherited stdin/stdout/stderr or redirected files
→ fd 1 becomes invalid inside wg
→ wg tries write(1)
→ the kernel returns EBADFThat is why wg --version appeared to print nothing.
Confirming the Cause with aa-complain
To prove that AppArmor was the cause, I installed the AppArmor utilities:
sudo apt update
sudo apt install -y apparmor-utilsThen I switched the wg profile to complain mode:
sudo aa-complain /etc/apparmor.d/wgAfter that, I tested the same command again:
rm -f /tmp/wg.stdout /tmp/wg.stderr
/usr/bin/wg --version > /tmp/wg.stdout 2> /tmp/wg.stderr
wc -c /tmp/wg.stdout /tmp/wg.stderr
cat -A /tmp/wg.stdout
cat -A /tmp/wg.stderrOutput:
71 /tmp/wg.stdout
0 /tmp/wg.stderr
71 total
wireguard-tools v1.0.20250521 - https://git.zx2c4.com/wireguard-tools/This confirmed the cause.
When the wg profile was enforced, AppArmor denied file_inherit.
When the wg profile was switched to complain mode, the same command worked.
After testing, switch the profile back to enforce mode:
sudo aa-enforce /etc/apparmor.d/wgWhy It Worked on My Local Ubuntu 26.04 VM
My local Ubuntu 26.04 VM used a normal console or SSH shell.
The EC2 instance was different because I connected through AWS SSM Session Manager.
In this case, the shell was running under:
snap.amazon-ssm-agent.amazon-ssm-agentThen, when that shell executed /usr/bin/wg, AppArmor transitioned the process into the wg profile.
The local VM did not have the same SSM Snap AppArmor confinement chain, so wg worked normally there.
Quick Workaround for Displaying wg Output
wg --version 2>&1 | catThis works because stdout becomes a pipe, and wg can write to it successfully.
However, this is only a workaround for displaying output.
It is not the clean fix for configuring WireGuard.
Correct Fix: Keep WireGuard Files Under /etc/wireguard
The clean fix for WireGuard key and configuration files is to use the path that the Ubuntu wg AppArmor profile already allows:
/etc/wireguard/Do not generate WireGuard keys under /tmp when the wg AppArmor profile is in enforce mode.
Avoid this:
wg genkey > /tmp/ec2_private.key
wg pubkey < /tmp/ec2_private.key > /tmp/ec2_public.keyThe AppArmor logs showed denials for files under /tmp, including key files.
Instead, create the WireGuard directory:
sudo install -d -m 700 /etc/wireguardGenerate the private key directly under /etc/wireguard/:
sudo sh -c 'umask 077; wg genkey > /etc/wireguard/ec2_private.key'Generate the public key:
sudo sh -c 'wg pubkey < /etc/wireguard/ec2_private.key > /etc/wireguard/ec2_public.key'Print the public key:
sudo cat /etc/wireguard/ec2_public.keyThis keeps WireGuard files in the path expected by the AppArmor profile.
Did this guide save you time?
Support this site