Hardening Debian for Privacy and Security: Part 1 March 27, 2026
The following is a guide on hardening Debian (and Debian-based distributions like MX Linux or Ubuntu) for security and privacy. This should not be considered professional advice; it is a list of tips. You are responsible for your own machine and anything that happens on it. If you brick it - that's your fault. Keep in mind that this guide is not sufficient to protect you from malicious State actors.
This guide assumes you have basic Linux knowledge, know how to use a terminal, and have some familiarity with terms like BIOS and UEFI. If any of this is over your head, you should study up.
Baseline
Full Disk Encryption using LUKS
Enable this during the initial OS installation to protect data at rest. You will need to use a strong passcode. If someone gains access to your physical hard drive and you have not encrypted it, or you are using a weak password for the encrypted drive(s), you're doomed. They will have the ability to access all of your files, and in the case of a weak password, they will crack it with ease.
How to choose a passcode
Write down a list of words separated by a delimiter, commit this to memory, and then destroy the paper. You will need to enter this each time you start your machine. If you ever forget the passcode, you will lose the ability to login permanently, and all of your files will be lost. It is imperative that you either commit the passcode to memory or otherwise find a way to store it securely so that you can retrieve it in case you ever forget it.
One way to create a strong passcode is by looking around at items in your room and using those combined with a delimiter. For example carpet_walls_microphone (this is a very weak passcode, you'll need something much longer).
Network encryption (optional)
Use a trusted VPN and route your internet traffic through this. It is important to understand that using a VPN shifts the trust from your ISP to a third-party. If that third-party provider is malicious, they will have the ability to see and log all of your traffic and may sell it over to advertisers or turn it over to law enforcement upon request. Trusted VPNs keep no logs and use RAM-only servers. Providing a list of trusted VPNs is outside of the scope of this blog post.
Physical kill switches
If you are on a desktop, you should invest in USB ports with physical switches (buttons) and plug your devices (webcams and microphones) into them. Toggle the ports off when you are not using your devices. This will guarantee that an attacker will not be able to gain access to your hardware through these devices.
Pre-Boot Hardware Security (BIOS/UEFI)
If an attacker has physical access to your machine, they might try to boot a malicious Live USB to tamper with your system. A BIOS/UEFI Administrator password prevents unauthorized changes to your boot order and hardware settings.
For standard motherboards, this is usually found under the Security tab. Use a search engine to find a guide for your specific motherboard on how to enable an admin password from the BIOS/UEFI, and choose a strong alphanumeric password.
GRUB Bootloader Password
A BIOS/UEFI administrator password prevents unauthorized changes to your boot order and hardware settings. It does not, however, protect the GRUB bootloader itself. An attacker with brief physical access—even without touching your BIOS—can interrupt the GRUB boot process, press e to edit the kernel command line, and append init=/bin/bash or single to drop into a root shell before your login screen ever appears. LUKS encryption provides strong protection here, but a GRUB password adds a second layer of defense.
First, generate a hashed password. You will be prompted to enter and confirm it:
grub-mkpasswd-pbkdf2
Copy the full grub.pbkdf2.sha512.... string it outputs. Then open or create /etc/grub.d/40_custom and add the following, replacing the hash placeholder with your actual output:
set superusers="root"
password_pbkdf2 root grub.pbkdf2.sha512.YOUR_FULL_HASH_HERE
Regenerate your GRUB configuration to apply it:
sudo update-grub
After rebooting, editing any kernel boot parameter will require entry of this password.
Kernel Hardening
The Linux kernel can be instructed to drop certain types of network traffic and restrict memory access. Open or create /etc/sysctl.conf and add the following parameters to the bottom of the file:
Disable IPv6 (Privacy)
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
net.ipv6.conf.lo.disable_ipv6 = 1
Enable TCP SYN Cookies (Security)
net.ipv4.tcp_syncookies = 1
Restrict ptrace
kernel.yama.ptrace_scope = 1
Note: Setting this to 1 is the sweet spot for developers writing in C, C++, or Python. It blocks rogue scripts from attaching to arbitrary programs, but still allows you to use debuggers like gdb on your own compiled binaries without needing root access. Setting it to 2 would break local debugging.
Disable Core Dumps (Privacy)
When an application crashes, the OS writes a core dump—a raw binary snapshot of the application's working memory at the exact moment of failure. That memory may contain passwords, session tokens, private keys, decrypted file contents, or any other sensitive data the application was handling. These files persist on disk and can be read by any process with sufficient permissions. Disable them system-wide:
kernel.core_pattern=|/bin/false
fs.suid_dumpable = 0
Additionally, enforce a hard system-wide limit via /etc/security/limits.conf:
* hard core 0
Apply the changes instantly with: sudo sysctl -p
Network Obfuscation & Firewall Rules
MAC Address Randomization
Prevent network admins and public Wi-Fi routers from tracking your physical device across sessions by randomizing your MAC address. Create a file at /etc/NetworkManager/conf.d/mac-rand.conf and add the following:
[device]
wifi.scan-rand-mac-address=yes
[connection]
wifi.cloned-mac-address=random
ethernet.cloned-mac-address=random
Restart the service depending on your init system:
- Standard Debian/Ubuntu (systemd):
sudo systemctl restart NetworkManager - MX Linux (SysVinit):
sudo service network-manager restart
Encrypted DNS (dnscrypt-proxy)
A VPN protects your traffic in transit, but DNS queries are frequently sent before the VPN tunnel is fully established, or they leak outside it entirely depending on how your VPN client handles DNS. Unencrypted DNS is one of the most reliable surveillance channels available to ISPs, network administrators, and passive network observers—every domain you visit is readable in plaintext.
dnscrypt-proxy encrypts your DNS queries using the DNSCrypt or DNS-over-HTTPS (DoH) protocol and forwards them exclusively to resolvers that have committed to keeping no logs.
There are two ways to deploy this. If you have a Pi-hole on your local network, Option B is strongly recommended — it is architecturally cleaner, protects every device on your network, and avoids the VPN conflict described below. Option A covers installation directly on the workstation.
Option A: Install on the Workstation Directly
What went wrong in the naive setup
The obvious approach — setting nameserver 127.0.0.1 in /etc/resolv.conf and then locking the file with sudo chattr +i — creates two problems:
Problem 1: Your VPN client needs to write its own DNS settings to /etc/resolv.conf when it connects. The immutable flag blocks this entirely, which is why you see unable to set system DNS server on connect.
Problem 2: A chicken-and-egg dependency. If dnscrypt-proxy uses your VPN tunnel to reach its upstream resolvers (which it often does to prevent your ISP from seeing encrypted DNS traffic), then DNS must work before the VPN connects, but the VPN needs DNS to connect. The two services deadlock.
The fix for both problems is to tell NetworkManager to stop managing /etc/resolv.conf entirely, and to handle the file yourself — without the immutable flag. This way your VPN client stops trying to overwrite it (because NM is no longer instructing it to), and dnscrypt-proxy stays in control of DNS at all times.
Installation
sudo apt install dnscrypt-proxy
Edit /etc/dnscrypt-proxy/dnscrypt-proxy.toml and set:
listen_addresses = ['127.0.0.1:53']
require_nolog = true
require_nofilter = true
require_nolog = true ensures dnscrypt-proxy will only use resolvers that have declared a no-logging policy. If no such resolvers are reachable, it will refuse to resolve rather than silently fall back to a logging resolver.
Stop NetworkManager from managing DNS
Edit /etc/NetworkManager/NetworkManager.conf and add dns=none under the [main] section:
[main]
dns=none
This tells NetworkManager — and any VPN plugin it controls — to stop writing to /etc/resolv.conf. You are now fully in control of it. Do not use chattr +i. The file needs to remain writable by root for legitimate manual changes.
Restart NetworkManager:
- MX Linux (SysVinit):
sudo service network-manager restart - Standard Debian/Ubuntu (systemd):
sudo systemctl restart NetworkManager
Now point the system resolver at localhost:
sudo nano /etc/resolv.conf
nameserver 127.0.0.1
SysVinit (Devuan / MX Linux)
The dnscrypt-proxy package on Debian ships with a systemd unit file only — no SysVinit init script is included. You need to write one manually. This is a common gap when using systemd-native packages on SysVinit systems.
First, confirm where the binary landed:
which dnscrypt-proxy
# or
ls /usr/sbin/dnscrypt-proxy /usr/bin/dnscrypt-proxy 2>/dev/null
Create the init script:
sudo nano /etc/init.d/dnscrypt-proxy
Paste this in full:
#!/bin/sh
### BEGIN INIT INFO
# Provides: dnscrypt-proxy
# Required-Start: $network $remote_fs $syslog
# Required-Stop: $network $remote_fs $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: DNSCrypt proxy
# Description: Encrypted DNS proxy supporting DNSCrypt and DNS-over-HTTPS
### END INIT INFO
PATH=/sbin:/usr/sbin:/bin:/usr/bin
NAME=dnscrypt-proxy
DAEMON=/usr/sbin/dnscrypt-proxy
CONFIG=/etc/dnscrypt-proxy/dnscrypt-proxy.toml
PIDFILE=/var/run/$NAME.pid
SCRIPTNAME=/etc/init.d/$NAME
[ -x "$DAEMON" ] || exit 0
. /lib/init/vars.sh
. /lib/lsb/init-functions
do_start() {
start-stop-daemon --start --quiet --background --make-pidfile \
--pidfile $PIDFILE --exec $DAEMON -- -config $CONFIG \
|| return 2
}
do_stop() {
start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 \
--pidfile $PIDFILE --name $NAME
RETVAL="$?"
rm -f $PIDFILE
return "$RETVAL"
}
case "$1" in
start)
log_daemon_msg "Starting $NAME"
do_start
log_end_msg $?
;;
stop)
log_daemon_msg "Stopping $NAME"
do_stop
log_end_msg $?
;;
restart|force-reload)
log_daemon_msg "Restarting $NAME"
do_stop
sleep 1
do_start
log_end_msg $?
;;
status)
status_of_proc -p $PIDFILE "$DAEMON" "$NAME"
exit $?
;;
*)
echo "Usage: $SCRIPTNAME {start|stop|restart|force-reload|status}" >&2
exit 3
;;
esac
If the binary is at /usr/bin/dnscrypt-proxy instead of /usr/sbin/dnscrypt-proxy, update the DAEMON= line before continuing.
Make it executable, register it, and start it:
sudo chmod +x /etc/init.d/dnscrypt-proxy
sudo update-rc.d dnscrypt-proxy defaults
sudo service dnscrypt-proxy start
sudo service dnscrypt-proxy status
It will now appear in MX Services after update-rc.d registers it.
systemd (Standard Debian / Ubuntu)
sudo systemctl enable --now dnscrypt-proxy
Verify DNS is working
With the service running, test resolution:
dig google.com
To confirm DNS is not leaking your real IP:
curl -s https://ipleak.net/json/
Check the dns field in the returned JSON. It should show an address belonging to your chosen dnscrypt-proxy resolver — not your ISP.
Option B: Install on Pi-hole (Recommended)
If you have a Pi-hole on your local network, install dnscrypt-proxy there instead of on your workstation. This is the architecturally correct approach for three reasons:
- Every device on your network gets encrypted DNS automatically, not just this machine
- Your VPN client can manage your workstation's DNS freely without any conflict
- Pi-hole continues to handle ad blocking as normal, with dnscrypt-proxy as its encrypted upstream
Workstation → Pi-hole (192.168.1.xxx) → dnscrypt-proxy → encrypted resolver
Your workstation's /etc/resolv.conf simply points at the Pi-hole, exactly as it did before you started any of this:
nameserver 192.168.1.xxx
No chattr, no chicken-and-egg VPN problem.
On the Workstation: Point DNS at the Pi-hole
Edit /etc/resolv.conf and set it to your Pi-hole's LAN address:
nameserver 192.168.1.xxx
Then stop NetworkManager from overwriting this file on reconnect. Edit /etc/NetworkManager/NetworkManager.conf and add dns=none under [main]:
[main]
dns=none
Restart NetworkManager:
- MX Linux (SysVinit):
sudo service network-manager restart - Standard Debian/Ubuntu (systemd):
sudo systemctl restart NetworkManager
Do not use chattr +i here. The file must remain writable — dns=none is the correct mechanism for keeping NM out of it.
VPN note: Set a custom DNS server in your VPN client pointing at 192.168.1.xxx and enable local network sharing in your VPN client settings. This is required because VPN clients like Mullvad manage DNS independently of NetworkManager — they will overwrite /etc/resolv.conf on connect regardless of the dns=none setting. Setting the custom DNS tells the VPN client to write 192.168.1.xxx into resolv.conf instead of its own servers. Local network sharing must be enabled simultaneously, otherwise the VPN tunnel blocks access to LAN addresses and the Pi-hole becomes unreachable even though resolv.conf points at it. Without both settings active together, DNS will fail on VPN connect. Note that this behavior is specific to clients like Mullvad that manage DNS at their own layer — other VPN clients that rely entirely on NetworkManager for DNS may behave differently and work with dns=none alone.
On the Pi: Install dnscrypt-proxy
SSH into your Pi:
ssh pi@192.168.1.xxx
Install dnscrypt-proxy:
sudo apt install dnscrypt-proxy
Configure dnscrypt-proxy
The Raspbian package ships with systemd socket activation enabled by default. This means the socket unit owns and binds the port, then hands it to the service as a file descriptor at runtime. You must work with this design rather than against it. If you set listen_addresses to a specific port in the config while the socket unit is also binding that port, the two will conflict and the service will fail to start.
The correct approach is to leave listen_addresses empty so the service uses the socket-activated file descriptor, and configure the socket unit to bind the port you want.
First, edit /etc/dnscrypt-proxy/dnscrypt-proxy.toml and set:
listen_addresses = []
require_nolog = true
require_nofilter = true
The empty listen_addresses tells dnscrypt-proxy to accept the port binding from the socket unit rather than attempting to bind it directly.
Next, create a socket unit override to change the port from the default 127.0.2.1:53 to 127.0.0.1:5053. Pi-hole already occupies port 53, so dnscrypt-proxy must use a different port. The empty assignments below are intentional — they clear the hardcoded defaults before setting the new values:
sudo mkdir -p /etc/systemd/system/dnscrypt-proxy.socket.d
sudo nano /etc/systemd/system/dnscrypt-proxy.socket.d/override.conf
[Socket]
ListenStream=
ListenDatagram=
ListenStream=127.0.0.1:5053
ListenDatagram=127.0.0.1:5053
Reload systemd and start the socket unit. Always start the socket unit directly — not the service. The socket unit activates the service automatically when needed:
sudo systemctl daemon-reload
sudo systemctl stop dnscrypt-proxy.service
sudo systemctl stop dnscrypt-proxy.socket
sudo systemctl enable dnscrypt-proxy.socket
sudo systemctl start dnscrypt-proxy.socket
Verify it is bound to the correct address and port:
sudo ss -tulnp | grep dnscrypt
You should see 127.0.0.1:5053. Wait a few seconds for dnscrypt-proxy to fetch its resolver list, then test:
sleep 5
dig google.com @127.0.0.1 -p 5053
A valid answer section confirms dnscrypt-proxy is resolving correctly through the encrypted upstream.
In Pi-hole: Point upstream DNS at dnscrypt-proxy
Log into your Pi-hole admin panel at http://192.168.1.xxx/admin.
Go to Settings → DNS. Uncheck every upstream DNS provider that is currently selected. Scroll down to Custom upstream DNS servers and enter:
127.0.0.1#5053
The #5053 notation tells Pi-hole to query port 5053 rather than the default 53. Save.
Restart pihole-FTL to pick up the change:
sudo service pihole-FTL restart
Verify Pi-hole is forwarding through dnscrypt-proxy correctly:
sleep 3
dig google.com @192.168.1.xxx
A valid response here means the full chain is working: Pi-hole received the query, forwarded it to dnscrypt-proxy on port 5053, and dnscrypt-proxy resolved it via an encrypted upstream.
Choosing a DNS upstream for your VPN provider
By default, dnscrypt-proxy selects the fastest available resolver from its public list, which is typically Cloudflare. This creates a DNS leak when using a VPN: your traffic exits through the VPN tunnel, but your DNS requests arrive at Cloudflare from your Pi-hole's real home IP. Any VPN provider's leak checker will correctly flag this.
The fix is to pin dnscrypt-proxy to resolvers operated by your VPN provider or to a trusted neutral resolver. Edit /etc/dnscrypt-proxy/dnscrypt-proxy.toml on your Pi and find or add the server_names line.
Mullvad VPN:
server_names = ['mullvad-doh']
Mullvad operates public DoH resolvers that are included in dnscrypt-proxy's default resolver list. Pinning to this server means all DNS queries are encrypted and routed through Mullvad's infrastructure, which their leak checker will confirm as clean.
ProtonVPN:
ProtonVPN operates DNS servers at 10.2.0.1 (only reachable inside the tunnel) and public resolvers accessible without the tunnel. For the Pi-hole setup where the Pi itself is not inside the VPN tunnel, use their public DoH resolver:
server_names = ['quad9-doh-ip4-port443-nofilter-ecs-pri']
ProtonVPN recommends Quad9 as a trusted neutral upstream when their tunnel resolver is not reachable. Alternatively, configure ProtonVPN's custom DNS in their client settings to point at 192.168.1.xxx and let the Pi-hole handle it — ProtonVPN's client supports custom DNS directly.
No VPN or provider-agnostic setup:
If you are not using a VPN or want a resolver that is not tied to any single provider, use a neutral no-log DoH resolver. Good options already in dnscrypt-proxy's public list:
# AdGuard — no-log, no-filter
server_names = ['adguard-dns-doh']
# Quad9 — no-log, blocks malicious domains
server_names = ['quad9-doh-ip4-port443-nofilter-pri']
# Use multiple resolvers for redundancy — dnscrypt-proxy will use the fastest
server_names = ['adguard-dns-doh', 'quad9-doh-ip4-port443-nofilter-pri']
After changing server_names, restart the socket unit and verify:
sudo systemctl stop dnscrypt-proxy.service
sudo systemctl stop dnscrypt-proxy.socket
sudo systemctl start dnscrypt-proxy.socket
sleep 5
dig google.com @127.0.0.1 -p 5053
Then check your VPN provider's leak test page with the VPN active. DNS entries in the results should show infrastructure belonging to your chosen upstream, not your ISP.
What happens when the VPN is off:
DNS continues to work normally through the Pi-hole and dnscrypt-proxy. Queries are still encrypted in transit — your ISP cannot read them. The difference is that your chosen upstream resolver (Mullvad, AdGuard, etc.) sees your real home IP rather than your VPN exit IP. This is expected behavior and not a misconfiguration. Your ISP sees only encrypted DoH traffic going to the resolver, not the contents of your queries.
Verify end to end from the workstation
With VPN disconnected:
dig google.com
curl -s https://ipleak.net/json/
With VPN connected:
cat /etc/resolv.conf # should still show 192.168.1.xxx
dig google.com
curl -s https://ipleak.net/json/
In the ipleak output, the ip field should show your VPN exit IP and the dns entries should show your dnscrypt-proxy upstream resolver — not your ISP. If both are true, the full chain is working correctly end to end.
UFW (Uncomplicated Firewall)
Standard Debian does not install UFW by default (it uses nftables). If you are on standard Debian, install it first:
sudo apt install ufw
Set a strict baseline that drops incoming traffic:
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw logging on
The Local Web Dev Exemption
If you spin up local Node.js or Python web servers (e.g., on port 8080) and need to test them on your phone or another device on your Wi-Fi, open the port only to your local subnet (find your subnet with ip a, usually something like 192.168.1.0/24):
sudo ufw allow from 192.168.1.0/24 to any port 8080
sudo ufw enable
Pruning Background Services
Less running code means a smaller attack surface. You should disable any daemons you don't actively use. Common candidates to disable include:
avahi-daemon(mDNS network discovery—disable to stay invisible on LAN)cups&cups-browsed(Printing subsystem—only disable this if you don't have a physical printer)nmbd&smbd(Samba file sharing)rpcbind/nfs-*(Network File Systems)
A note on SSH: Debian and MX Linux typically only install the SSH client by default. Unless you manually installed openssh-server, you do not have a daemon listening on port 22, so there is no service to disable. You can still safely git push or SSH into remote servers.
How to disable services:
For Standard Debian / Ubuntu (systemd):
To stop a service immediately and prevent it from starting on boot, use systemctl. For example, to disable the printing service:
sudo systemctl disable --now cups
To check what services are currently running: systemctl list-units --type=service --state=running
For MX Linux (SysVinit):
Open the MX Services GUI tool from your application launcher, find the services in the list, and uncheck them to "Disable at boot."
Application Sandboxing
Do not run proprietary or highly-targeted applications (like Discord, Zoom, or web browsers) with full user permissions via apt. If they are compromised, they have access to your SSH keys, source code, documents, and the rest of your home directory.
- System Tools & Core Utilities: Install via
aptfor stability and tight system integration. - Proprietary & Web-Facing Apps: Install via Flatpak. Use the GUI tool Flatseal to revoke unnecessary permissions—microphone, camera, network access, or
/homedirectory access—on a strict, per-application basis.
Firejail
Flatpak with Flatseal handles proprietary apps well, but many applications you install via apt cannot or should not be moved to Flatpak—command-line utilities, scripts, or tools that require tight integration with the system. For these, use Firejail.
Firejail uses Linux kernel namespaces and seccomp-bpf syscall filters to drop an application into a restricted environment with minimal filesystem visibility. It ships with pre-built security profiles for hundreds of common applications.
sudo apt install firejail
To run an application sandboxed manually:
firejail firefox
firejail vlc
To make sandboxing permanent—so that applications always launch sandboxed from your application menu automatically, without requiring any changes to how you open them:
sudo firecfg
To inspect what a profile permits or restricts:
cat /etc/firejail/firefox.profile
For applications that do not have a pre-built profile, Firejail's defaults still provide meaningful isolation by restricting filesystem visibility and dropping dangerous syscalls. You can write custom profiles for your own tools by placing a .profile file in ~/.config/firejail/.
SSH Server Hardening
As noted in the pruning section, Debian and MX Linux do not install openssh-server by default. If you have not manually installed it, you have no SSH daemon running and no port 22 exposed—skip this section. However, if you do install it to accept remote connections, the default configuration has several dangerous settings that must be locked down immediately.
Edit /etc/ssh/sshd_config and set the following:
PermitRootLogin no
PasswordAuthentication no
AuthenticationMethods publickey
X11Forwarding no
AllowTcpForwarding no
MaxAuthTries 3
LoginGraceTime 30
AllowUsers your_username_here
On PasswordAuthentication no: This disables password-based login entirely and forces SSH key authentication, which eliminates brute-force password attacks completely. Before enabling this, you must generate an SSH keypair and copy the public key to the server. If you enable this setting before doing so, you will lock yourself out and require physical machine access to recover.
Generate a keypair on your client machine and copy the public key to the server:
ssh-keygen -t ed25519 -C "your_label"
ssh-copy-id your_username@your_server_ip
After saving sshd_config, restart the daemon:
- Standard Debian/Ubuntu (systemd):
sudo systemctl restart ssh - MX Linux (SysVinit):
sudo service ssh restart
Application Privacy & Anti-Tracking
Even with a hardened OS and network, individual applications can still leak your data or location. Here are a few final tweaks for your daily drivers.
Timezone & Network Time Protocol (NTP)
If your system clock is synced to your real local time but you are using a VPN to appear in another country, websites can use JavaScript to read your local clock and expose your real timezone. Furthermore, your computer's background NTP pings to public servers act as a "heartbeat" that tells observers exactly when you are active.
To fix this:
- Set your system clock to UTC.
- Identify and disable your NTP service. Common names are
ntp,ntpd, orchrony.
Check which one you have:
ls /etc/init.d/ | grep -E 'ntp|chrony'
To disable it:
- Debian (systemd):
sudo timedatectl set-ntp false - MX Linux (SysVinit):
sudo update-rc.d <service_name> disable(sudo service chrony stopto stop it for the current session) (Replacewith the name found above, e.g., chrony)
To keep your clock from drifting without pinging the public internet, use a local server (like a Raspberry Pi) as your network's single "source of truth." Your Pi pings the internet 24/7 (camouflaging your specific activity), and your workstation only pings the Pi over the local network.
See this guide for information on setting up a local NTP server for enhanced privacy and security.
Disable Firefox Telemetry
Firefox is highly recommended for privacy, but its default settings still phone home to Mozilla.
- Open Firefox and go to Settings > Privacy & Security.
- Scroll down to Firefox Data Collection and Use.
- Uncheck every box in this section.
- Advanced: Type
about:configin your URL bar, search fortoolkit.telemetry.enabled, and set it tofalse.
Disable VSCode Telemetry
If you use Microsoft's Visual Studio Code, it collects usage data and crash reports by default.
- Open VSCode and go to Settings (
Ctrl + ,). - Search for
telemetry. - Change the Telemetry: Telemetry Level dropdown to
off. - Better Alternative: Consider switching to VSCodium. It is the exact same editor, built from the exact same open-source Microsoft repository, but with all Microsoft telemetry and tracking completely ripped out at the compiler level.
Advanced Extras
AppArmor (Mandatory Access Control)
AppArmor confines standard apt packages to a defined set of files and network resources using path-based policies. If a core utility is compromised, AppArmor restricts the attacker to only the resources that application is explicitly permitted to access—blocking lateral movement to your sensitive files even if the process is running with elevated privileges.
Standard Debian ships with AppArmor present but often without the extended community profiles installed or enforced. Fix that:
sudo apt install apparmor-profiles apparmor-utils apparmor-profiles-extra
sudo aa-enforce /etc/apparmor.d/*
What to do when something breaks: Enforcing all profiles at once is aggressive and will break some applications. When an app stops working after you run aa-enforce, check AppArmor's audit log for denials using the interactive repair tool:
sudo aa-logprof
aa-logprof reads recent AppArmor denials from the system log and walks you through updating the offending profile to permit legitimate behavior while keeping the confinement intact. If an application is broken and you need it working immediately, set that specific profile to complain mode (logs violations but does not block them) while all others remain enforced:
sudo aa-complain /etc/apparmor.d/usr.bin.application_name
To audit the current enforcement status of all profiles:
sudo apparmor_status
AppArmor is path-based, which makes it less granular than SELinux's type enforcement model. However, it is substantially easier to configure and maintain, and is the correct choice for a Debian or MX Linux desktop environment.
Mount /tmp in RAM (tmpfs)
By default, /tmp writes to your physical disk. Sensitive temporary data—partial file downloads, decrypted fragments, session tokens written by applications—persists on disk after a crash or forced shutdown. This is true even with LUKS enabled, because LUKS is already unlocked at runtime. Mounting /tmp as a RAM-based tmpfs filesystem guarantees that all temporary files are destroyed the instant the machine powers off.
Add the following line to /etc/fstab:
tmpfs /tmp tmpfs defaults,noatime,nosuid,nodev,noexec,size=512M 0 0
Adjust size=512M to suit your available RAM. As a rule of thumb, one quarter of your total RAM is a safe default. Apply without rebooting:
sudo mount -a
To verify it is mounted correctly:
mount | grep /tmp
Preventing WebRTC Leaks in Firefox
Even with a strict VPN, modern browsers use WebRTC for peer-to-peer voice and video calls. WebRTC is notorious for bypassing VPN tunnels and leaking your true physical IP address to websites.
- In Firefox, type
about:configin the URL bar. - Search for
media.peerconnection.enabled. - Toggle the value to
false. (Note: This will break browser-based voice/video calls like Google Meet or Discord web).
USB Physical Security (USBGuard)
If you walk away from your machine, an attacker could plug in a "BadUSB" (a malicious device disguised as a flash drive that acts as a keyboard to rapidly inject payloads). USBGuard creates a strict whitelist for USB devices and blocks anything newly plugged in while your screen is locked.
First, install and generate the policy based on what is currently plugged in:
sudo apt install usbguard
sudo usbguard generate-policy > /tmp/rules.conf
sudo install -m 0600 -o root -g root /tmp/rules.conf /etc/usbguard/rules.conf
Next, restart the service depending on your init system:
- Standard Debian/Ubuntu (systemd):
sudo systemctl restart usbguard - MX Linux (SysVinit):
sudo service usbguard restart
CPU Microcode Updates
Debian's strict adherence to open-source software means proprietary CPU firmware is often excluded by default. If you use an Intel or AMD processor, you need these packages to protect against hardware-level vulnerabilities like Spectre and Meltdown. Make sure your non-free-firmware repository is enabled, then run:
# For Intel processors
sudo apt install intel-microcode
# For AMD processors
sudo apt install amd64-microcode
Reboot your machine to apply the hardware patches.
Kernel Audit Logging (auditd)
auditd is the Linux kernel's dedicated security audit daemon. Unlike journald, which logs application-level events, auditd operates at the syscall level—it records every file access, privilege escalation, user login, and process execution directly from the kernel before any userspace code can interfere with or suppress the record. It is the audit mechanism used by the NSA, DoD, and every major compliance framework (PCI-DSS, HIPAA, DISA STIG).
Install it:
sudo apt install auditd audispd-plugins
Add a minimal ruleset that covers the most critical surveillance points. Open /etc/audit/rules.d/audit.rules and add:
# Watch for changes to critical authentication files
-w /etc/passwd -p wa -k identity
-w /etc/shadow -p wa -k identity
-w /etc/sudoers -p wa -k sudoers
# Log all privilege escalation via sudo
-w /usr/bin/sudo -p x -k privilege_escalation
# Log unauthorized access attempts to sensitive files
-a always,exit -F arch=b64 -S open -F exit=-EACCES -k access_denied
-a always,exit -F arch=b64 -S open -F exit=-EPERM -k access_denied
# Log changes to network configuration
-w /etc/hosts -p wa -k network
-w /etc/NetworkManager/ -p wa -k network
Enable and start the service:
- Standard Debian/Ubuntu (systemd):
sudo systemctl enable --now auditd - MX Linux (SysVinit):
sudo service auditd startand enable it via MX Services
To search audit logs for specific events—for example, all sudoers file changes:
sudo ausearch -k sudoers
To generate a summary report of recent audit activity:
sudo aureport --summary
auditd logs are written to /var/log/audit/audit.log. Unlike application logs, these records are written by the kernel and are significantly harder for an attacker to tamper with after the fact.
Latest posts
- Hardening Debian to Military Standards March 28, 2026
- Hardening Debian for Privacy and Security: Part 2 March 28, 2026
- systemd is trash, here's why March 28, 2026
- Japanese hyperpop March 27, 2026
- Harden your Raspberry Pi March 27, 2026
- Using a local server as source of truth for your time sync March 27, 2026