joe curlee

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 apt for 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 /home directory 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:

  1. Set your system clock to UTC.
  2. Identify and disable your NTP service. Common names are ntp, ntpd, or chrony.

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 stop to stop it for the current session) (Replace with 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:config in your URL bar, search for toolkit.telemetry.enabled, and set it to false.

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:config in 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 start and 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

View more posts