Introduction
privacy-book
Notes, posts, and more
Latest posts
Recent notes
Popular tags
RethinkDNS
✔️ Click to Expand Table of Contents

I’m not affiliated with RethinkDNS in any way, I’m just a technologist and privacy advocate.
🔑 Key Terms
✔️ Click to Expand Key Terms
-
HTTP (HyperText Transfer Protocol): The standard protocol used by web browsers and servers to transfer web pages and related resources over the internet.
-
HTTPS: HTTP encrypted using TLS is often referred to as HTTPS. TLS-encrypted web traffic is by convention exchanged on port 443 by default, while unencrypted HTTP uses port 80 by default.
-
TLS (Transport Layer Security) is an encryption protocol and is the standard for enabling two networked applications or devices to exchange information privately and robustly.
-
Mixed content: is when an otherwise secure website uses resources fetched via HTTP or other insecure protocols.
-
IP (Internet Protocol): The address system of the internet that routes data packets from source to destination devices. IP operates at the network layer and does not guarantee delivery order or error checking, which is handled by TCP.
-
IP Address (Internet Protocol Address): A unique numeric label assigned to each device on a network, used to identify and locate the device for communication.
-
Subnet (Subnet Range): represents a block of IP addresses grouped together under a single rule. Instead of allowing or blocking individual IP addresses one by one, you define a subnet to include a wide range of IPs within that block.(Useful for apps that you
Isolate). -
Host: Any device connected to a network with an IP address, capable of sending and receiving data, including computers, phones, or servers.
-
Client: A device or software (often your computer or phone) that initiates requests to servers to access resources or services, forming the client-server model of communication.
-
Port: A port in networking is a virtual communication endpoint managed by a computer’s operating system that helps direct network traffic to specific apps or services. While an IP address identifies a device on a network, ports allow the system to know exactly which app or service should handle the incoming or outgoing data. Web traffic commonly uses port 80 (HTTP) or 443 (HTTPS), so when data arrives for those ports, it’s routed to the web server application on the device. When we block port 80, we block insecure HTTP connections.
-
TCP (Transmission Control Protocol) is responsible for maintaining a connection through a handshake and putting the packets in the correct order. TCP will also ask for missing pieces and is known as a reliable but slow protocol.
-
UDP (User Datagram Protocol) (UDP/IP): is a fast protocol used across the internet for time-sensitive transmissions such as DNS lookups or VoIP. UDP allows a computer to send data straight to another without requiring a handshake.
-
DNS (Domain Name System): stores domain information in a distributed database and translates domain names into IP addresses and vice versa. This enables us to only have to remember simple domain names rather than complex IP addresses.
Domain Name
ssd.eff.org | | | | | top-level domain | | | second-level domain | subdomain☝️ The hierarchy is read from right to left, the TLD is the highest-level domain (
.orghere), the second-level domain (eff) is directly to the left of the TLD, and anything further left (likessd) is a subdomain under that second-level domain.
-
DNS Server: When you search for a domain name (rethinkdns.com) it triggers a DNS lookup. Several different types of DNS servers typically work together to complete a single DNS lookup.
-
DNS Resolver: is a server or software component that translates domain names into IP addresses that devices use to communicate.
-
Unicast DNS: The traditional DNS setup where each DNS server is assigned a unique, single IP address. DNS queries from clients are directed to this specific server regardless of the clients location.
-
Anycast DNS: Anycast allows a single IP address to be used by multiple servers. In Anycast DNS, a DNS query can be answered by any one of several DNS servers, usually the one closest to the user by geographic or network proximity. This setup reduces latency, increases the availability of DNS resolution services, and offers protection against DNS flood DDoS attacks by distributing traffic across multiple servers.
-
Recursive resolver (DNS recursor): is typically the first stop in the series of the above servers.
-
Iterative resolver: In an iterative DNS query, each DNS server responds directly to the client with a referral to another server, and the client continues querying successive servers until it receives the IP address for the requested domain.
-
Proxy: A proxy, in relation to Orbot with Rethink, is an intermediary service that routes internet traffic from your device through the Tor network to provide privacy and anonymity.
-
HTTP(S) Proxy: An HTTP proxy is an intermediary server that forwards HTTP/HTTPS web traffic from a client (e.g., a browser or app) to destination servers, allowing for privacy, filtering, or routing control while masking the user’s IP. HTTP proxies only work with web traffic (HTTP/HTTPS).
-
SOCKS5 (Socket Secure 5): Is an internet proxy protocol that transfers info from one server to another while redirecting the user’s IP address. It supports both UDP and TCP and can actually improve speed in some cases.
-
pi-hole: a DNS sinkhole that protects your devices content without installing any client-side software.
-
OpenSnitch: is a GNU/Linux application firewall.
-
proxifier: a proxifier acts as a proxy client, routing specific application traffic through proxy servers without encrypting data or providing global IP masking.
-
VPN (Virtual Private Network): a VPN creates an encrypted tunnel that routes all network traffic from your device through a remote server, masking your IP address and securing your entire connection.
-
WireGuard: a modern VPN encryption protocol, its fast and has gained widespread adoption among VPN providers.
-
OpenVPN: an older, more mature VPN protocol that uses SSL/TLS for encryption. It’s known for being very reliable and highly configurable but tends to be slower and more complex than WireGuard. Good VPNs often give you the choice between protocols.
-
Bypass DNS and Firewall: Bypass the DNS and Firewall for this app, this only works with Rethink’s DNS. -
Bypass Universal: Bypass the Universal firewall for this app. -
Exclude: The app is excluded from the dns and firewall, Rethink is unaware of this app. -
Isolate: When an app is isolated, only trusted IPs are allowed. (i.e., IPs or domains you explicitly set trust rules for). -
🛜(Unmetered Wi-Fi): Wi-Fi settings, either blocked or allowed.
-
📶 (Metered mobile): Mobile data settings, either blocked or allowed.
RethinkDNS Overview
-
Bypass DNS and Firewall: Bypass the DNS and Firewall for this app, this only works with Rethink’s DNS. -
Bypass Universal: Bypass the Universal firewall for this app. -
Exclude: The app is excluded from the dns and firewall, Rethink is unaware of this app. -
Isolate: When an app is isolated, only trusted IPs are allowed. (i.e., IPs or domains you explicitly set trust rules for). -
🛜(Unmetered Wi-Fi): Wi-Fi settings, either blocked or allowed.
-
📶 (Metered mobile): Mobile data settings, either blocked or allowed.
The DNS mode routes all DNS traffic generated by all apps to any user chosen DNS-over-HTTPS, DNS-over-TLS, DNSCrypt, or Oblivious DNS-over-HTTPS resolver.
Firewalls like Rethink that block both UDP and TCP connections are usually sufficient because nearly all applications rely on these two protocols for their networking and communication. Almost every app communicates over TCP or UDP, so blocking these protocols effectively restricts most network traffic from and to apps, preventing them from connecting without permission.
I will share how I use RethinkDNS, obviously feel free to make changes based on your threat model and needs.
Different versions of the RethinkDNS have different features and capabilities. The best version in my opinion is the F-Droid version.
Getting Started
DNS
I was surprised to see that GrapheneOS recommends that people use their own “network provided DNS servers for general purpose usage” (i.e., Your ISPs DNS). They say that “using the network-provided DNS servers is the best way to blend in with other users”. Network and web sites can fingerprint and track users based on non-default DNS configurations. –grapheneos default-dns
Given that it is a fact that many ISPs are known to share this data and do other privacy disrespecting things, I would personally rather make it harder for them. I included this because they are the experts and the choice is yours, I use a custom DNS myself. I guess since so many people virtue-signal saying that they “have nothing to hide”(a reckless, privileged notion) that not many people truly care about privacy and are likely using the default. The more people that we can convince to use privacy respecting tools, the better it is for everybody.
Recommended Resolvers:
✔️ Click to Expand recommended resolvers
These are just the providers I’ve researched personally and not in a specific order. It’s recommended to regularly test and switch between DNS providers as location and network conditions play a factor. There are DNS benchmarking tools available to either download or use in-browser.
A few examples:
-
Quad9 is known to enforce strong standards in cryptography and was the first to use DNSSEC cryptographic validation.
-
Mullvad DNS
-
NextDNS
-
Cloudflare publishes regular transparency reports and undergoes independent audits and have a great blog explaining the technology which is a major plus. GrapheneOS sets Cloudflare as the fallback due to their good privacy policy, and widespread usage.
-
RethinkDNS: Their DNS code is open source so anyone can audit it themselves. They do not collect PII nor seek to profile its users, transparency is great.
Throughout this article, I’ll be discussing the RethinkDNS Android App. We’ll later reference the DNS Blocklist configuring website, which is available at rethinkdns.com/configure.
❗ NOTE: When you switch to an encrypted DNS resolver, you are shifting your trust from your ISP’s DNS servers to the third-party DNS provider you choose. Encryption protects your DNS queries from being seen or intercepted by outsiders, like your ISP or network eavesdroppers, which improves privacy. However, the DNS resolver itself still sees all your queries and could potentially log, analyze, or misuse that data.
That said, it’s quite common for ISPs to engage in practices that compromise user privacy. Do some research, whats their business model, privacy policy, etc. Unfortunately, with a VPN you are also just shifting the trust. Don’t blindly choose a VPN either.
Configure -> DNS -> Other DNS:
-
Choose the type of resolver you want, I use DNSCrypt. Once you click you can choose the specific resolver you want such as Quad9. You may notice that it says
Failed: using fallback DNS. This is only because we haven’t turned it on yet, we will recheck this once we turn it on. -
If you want a relay in a specific country, you can click the
Relaystab. For DNSCrypt you are given the choice between the Netherlands, France, Sweden, Los Angeles, and Singapore. You might do this if you were trying to circumvent censorship.
Rules set the following:
-
Advanced DNS filtering (experimental): Assign unique IP per DNS request. -
Prompt on blocklist updates: This is for if you use Rethink’s custom blocklists. -
Prevent DNS leaks: When enabled, Rethink captures all packets on port 53 and forwards it to a user-set DNS endpoint.
Leave all the Advanced defaults unless you plan on setting up a SOCKS5 proxy,
in which case you will want to enable Configure -> DNS -> Never proxy DNS.
F-Droid Specific:
-
Split DNS (experimental): Forward DNS queries from proxied apps to the proxy’s DNS servers. -
Treat DNS rules as firewall rules (experimental): DNS blocking will be bypassed during resolution; the decision will be made at connection time. -
Set
Use fallback DNS as bypass: where it always uses your fallback DNS for bypassed apps, domains and IPs. -
You can also set
Use System DNS for undelgated domainswhich is generally safe to enable and useful if access to local network devices or custom internal domains is needed.
Blocklists:
This is a cool feature, similar to NextDNS if I understand correctly. Since it’s a system-wide DNS filter it applys to any app that is run through Rethink, not only your browser, every app.
Blocklists are available when you use Rethink’s DNS.
Configure -> DNS -> Rethink DNS:
-
Choose between
Skywith higher uptime and a stub resolver at cloudflare.com, ORMaxwhich is more private and has its recursive resolver at fly.io. -
Choose between the preconfigured blocklists, OR Click
RDNS Plus,EDIT,ADVANCED, Search blocklists:hagezi,Multi Pro++ (HaGeZi),APPLY (RDNS PLUS) -
You can manually Check for an update to the blocklists, which you should because they are updated regularly with new identified threats. You can also enable
Prompt on blocklist update.
F-Droid & Github Versions
When on the F-Droid and GitHub versions of the Rethink, you can download
blocklists from Configure -> DNS -> On-device blocklists, and have them setup
for any DNS upstream.
There is a known bug where it sometimes when you click DOWNLOAD BLOCKLISTS it
just keeps listening and never receives anything. The GitHub v05.5n was
affected by this.
This does work on the F-Droid version.
For example, if you want to use ODoH with the HAGEZI Blocklist you could:
Go to Configure -> DNS -> Other DNS, Choose ODoH with Cloudflare. Start
it.
Now in Configure -> DNS -> RULES, tap On-device blocklists, and click the
disabled logo, then click DOWNLOAD BLOCKLISTS. Once the download is complete,
you can select Configure and select ADVANCED and search for HAGEZI, choose
MULTI PRO++ and maybe oisd small, then press APPLY (ON-DEVICE).
Now any app that doesn’t bypass Rethink will use your chosen blocklists, denying access to malicious URLs.
The HAGEZI blocklists are respected for being updated frequently. There are different levels with MULTI PRO++ (HAGEZI) being the highest and 4 other lest strict levels.
Network
Settings explained:
-
Loopback proxy forwarder apps: Enable when you want all of your devices proxy-related traffic to also flow through Rethinks encrypted tunnel. It ensures apps that proxy DNS/HTTP/SOCKS traffic don’t leak outside the VPN.
-
Do not route Private IPs: Prevents routing traffic destined for private IP address ranges (like 192.168.x.x, 10.x.x.x) through the tunnel. Instead, this traffic goes directly over the local network or system default route. Most useful when you want devices on your local network (NAS, printers, etc.) to be reachable by apps on your phone without routing the traffic through Rethinks tunnel, improving performance and reliability of local connections but possibly reducing privacy.
-
Loopback (experimental): When enabled, this routes RethinkDNS’s own internal traffic, such as blocklist downloads, connectivity checks, and DNS queries made by the app itself, back into the encrypted tunnel through the loopback device.
| Setting | Function | When to Use |
|---|---|---|
| Loopback Proxy Forwarder Apps | Route proxy-forwarder app traffic back into Rethink VPN tunnel | When you want consistent VPN/privacy coverage for proxies |
| Do Not Route Private IPs | Avoid routing local network/private IP traffic through VPN (send directly) | To access local devices without VPN bottlenecks |
| Loopback (Experimental) | Route Rethink’s own app traffic (updates/checks) back through VPN tunnel | To fully protect app’s own DNS/blocklist traffic |
NOTE: The above settings are disabled by default because some are experimental and can easily break functionality. I have found that the settings below give the most functionality without breakage 👇️:
Configure -> Network:
-
Set
Use all available networksto ON. This enables Wifi and mobile data to be used at the same time by Rethink. (Optional, may use more battery) -
Set your IP version: The default is
IPv4, you can choose betweenIPv6 (experimental)andAuto (experimental). -
Choose fallback DNS: When your user-preferred DNS is not reachable, fallback DNS will used.
- With the F-Droid version in
Configure -> DNS -> ADVANCED. You can enableUse fallback DNS as bypass, which always uses fallback DNS for bypassed apps, domains, and IPs.
- With the F-Droid version in
-
I personally disable
Enable network visibility, just keep in mind that some apps may break.- “Shutting this off prevents apps from accessing all available networks, stopping them from bypassing Rethinks tunnel”. This caused issues with the browser when turned off.(Some audio/video conference apps like Zoom and Meet require this)
Firewall
Configure -> Firewall -> Universal firewall rules and set the following to ON:
-
Block all apps when device is locked -
Block when DNS is bypassed -
Block port 80 (insecure HTTP) traffic
You can get more restrictive from here, but it will take some manual intervention to get everything working correctly.
Keep your firewall rules in mind when you’re configuring per app settings. If the Universal firewall blocks all apps when the device is locked, do you want this app affected by this or do you want to let it Bypass the Universal firewall?
Turn ON DNS and Firewall
Home 🏠:
- Click the big
Startbutton on the bottom of the screen and leave it set to the defaultDNS and Firewall (default)
Now that we’ve started the DNS and Firewall, we can go back to
Configure -> DNS and ensure the provider we chose started successfully. You
can also experiment with different types of resolvers, make sure to wait for
below the chosen resolver to say Connected.
Now, all apps on your device by default allow both Wi-Fi and mobile data access through the RethinkDNS encrypted tunnel. Try some of your most used Apps to see if they function correctly.
RethinkDNS’s firewall blocks or restricts any network traffic that isn’t explicitly allowed. Although by default all apps are allowed network access, some apps require special permissions or bypasses due to their network behavior. Many apps rely on multiple external services, backend APIs, etc. that may be blocked by the firewall.
Apps that Don’t work
I will use Reddit as an example, the process is the same for any app. Reddit’s
app and website rely on multiple third-party services and external domains
beyond just reddit.com itself.
For apps that don’t work it’s important to ensure that your Android systems
Private DNS is set to Automatic.
Home -> Apps:
Search for Reddit, click on it and the Firewall Rules For Reddit will pop up.
Since it is already allowed Unmetered and Metered connections and still
doesn’t work, we can try one setting at a time until it does work and this is
the same process for other Apps that aren’t working.
-
First, you should check your logs and see why it’s being blocked. Look at the domains involved and set trust rules for said domains. (start by trusting reddit.com). Check the logs again, add any domains that were blocked related to reddit. If it is unclear why networking still isn’t working, you can:
-
Bypass Universal, this allows it to bypass any Universal Firewall rules you have set. -
If you’re using Rethink’s DNS, you can try allowing the app to
Bypass DNS & Firewall. Try the app again, does it work? If not: -
You can also
Isolatean App, you then have to set up trust | allow rules for domains or IPs over a period of time which can take a while. You can go toAppsand search for the app in question, click on it and at the bottom of the screen you’ll seeIP Logs, andDomain Logsto help with this. -
Excludethe app. This makes RethinkDNS completely unaware of the app and is often what is required for Reddit. It is my understanding that after youExcludeReddit for example, your systems Automatic Secure DNS will pick it up.
Other Methods
Rather than watching the logs and setting trust rules over time, you could use
tools like nslookup and dig to resolve said domain and reveal IP ranges
used.
nslookup reddit.com
Server: 127.0.0.1
Address: 127.0.0.1#53
Non-authoritative answer:
Name: reddit.com
Address: 151.101.129.140
Name: reddit.com
Address: 151.101.193.140
Name: reddit.com
Address: 151.101.1.140
Name: reddit.com
Address: 151.101.65.140
Name: reddit.com
Address: 2a04:4e42::396
Name: reddit.com
Address: 2a04:4e42:600::396
Name: reddit.com
Address: 2a04:4e42:200::396
Name: reddit.com
Address: 2a04:4e42:400::396
Resolving a domain (like reddit.com) using tools like nslookup or dig
reveals multiple IPs because large services use multiple servers across CDNs and
networks for redundancy and performance.
You can then run whois on one of those IPs (e.g., whois 151.101.129.140) to
identify the subnet ranges owned by Reddit’s CDN provider (Fastly in this case),
which helps when setting up subnet range allow rules in Rethink.
✔️ Click to Expand `whois` Example Output
whois 151.101.129.140
#
# ARIN WHOIS data and services are subject to the Terms of Use
# available at: https://www.arin.net/resources/registry/whois/tou/
#
# If you see inaccuracies in the results, please report at
# https://www.arin.net/resources/registry/whois/inaccuracy_reporting/
#
# Copyright 1997-2025, American Registry for Internet Numbers, Ltd.
#
NetRange: 151.101.0.0 - 151.101.255.255
CIDR: 151.101.0.0/16
NetName: SKYCA-3
NetHandle: NET-151-101-0-0-1
Parent: RIPE-ERX-151 (NET-151-0-0-0-0)
NetType: Direct Allocation
OriginAS:
Organization: Fastly, Inc. (SKYCA-3)
RegDate: 2016-02-01
Updated: 2021-12-14
Ref: https://rdap.arin.net/registry/ip/151.101.0.0
OrgName: Fastly, Inc.
OrgId: SKYCA-3
Address: PO Box 78266
City: San Francisco
StateProv: CA
PostalCode: 94107
Country: US
RegDate: 2011-09-16
Updated: 2025-03-25
Ref: https://rdap.arin.net/registry/entity/SKYCA-3
OrgNOCHandle: FNO19-ARIN
OrgNOCName: Fastly Network Operations
OrgNOCPhone: +1-415-404-9374
OrgNOCEmail: noc@fastly.com
OrgNOCRef: https://rdap.arin.net/registry/entity/FNO19-ARIN
OrgTechHandle: FRA19-ARIN
OrgTechName: Fastly RIR Administrator
OrgTechPhone: +1-415-518-9103
OrgTechEmail: rir-admin@fastly.com
OrgTechRef: https://rdap.arin.net/registry/entity/FRA19-ARIN
OrgAbuseHandle: ABUSE4771-ARIN
OrgAbuseName: Abuse Account
OrgAbusePhone: +1-415-496-9353
OrgAbuseEmail: abuse@fastly.com
OrgAbuseRef: https://rdap.arin.net/registry/entity/ABUSE4771-ARIN
#
# ARIN WHOIS data and services are subject to the Terms of Use
# available at: https://www.arin.net/resources/registry/whois/tou/
#
# If you see inaccuracies in the results, please report at
# https://www.arin.net/resources/registry/whois/inaccuracy_reporting/
#
# Copyright 1997-2025, American Registry for Internet Numbers, Ltd.
⚠️ Use caution when setting trust rules for subnet ranges, as this grants access to all services hosted within that range, potentially including services unrelated to the one you intend to access. Best practice is to first attempt to trust the specific domain name (e.g., reddit.com), which is generally the most granular and secure method. Only consider trusting broader subnet ranges (like 151.101.0.0/16) as a last resort if domain-based rules fail to resolve connectivity issues for the intended service.
We can see that:
- The subnet range for that IP:
151.101.0.0 - 151.101.255.255(CIDR notation:151.101.0.0/16)
So as a starting point to get Reddit working we could trust the following subnet range:
-
IPv4:
151.101.0.0/16 -
This subnet covers the full range from
151.101.0.0to151.101.255.255, which includes all related IPs Reddit uses via Fastly’s CDN. -
The owning organization:
Fastly, Inc.& More…
Firefox Encrypted DNS through Rethink
First, make sure you can visit a few sites in Firefox. If you can, then your
browser traffic should be routed through the Rethink tunnel, we will check here.
If you can’t, go to Home -> Apps and search for Firefox, is networking
enabled?
RethinkDNS Settings
For the best experience routing your browser traffic through your custom endpoint (e.g., DNSCrypt) on both Wi-Fi and mobile data ensure the following are set:
- Do not turn on
Block any app not in usein the Universal firewall. After some Log digging, I found that this causes the browser to fail more often than not. Configure -> Network -> Enable network visibilityset to ON. I had experimented with turning this off and certain websites wouldn’t load when on Wi-Fi and none would load on mobile data. Turning it back on seemed to fix both with no leaks detected.
Double check that in Rethink’s Configure -> DNS -> Prevent DNS leaks is ON, as
well as the Universal Firewalls Block when DNS is bypassed ON.
Firefox Settings
In Firefox, plug
about:configinto the URL bar and scroll down tonetwork.trr.modeand change its value to3to prevent leaking DNS queries to the System resolver. Also inabout:configscroll down tomedia.peerconnection.enabled, double-click to set it to false to prevent WebRTC leaks.
IronFox no longer lets you adjust the network.trr.mode in about:config, you
adjust the trr.mode through the DNS over HTTPS settings. Set it to
Default Protection or OFF and check for dns leaks. Fennec also has issues
with setting network.trr.mode 3 and falls back to insecure methods. I would
suggest using the built in DNS over HTTPS for Fennec on max protection.
- The trade-off is that disabling WebRTC also disables any websites or apps using WebRTC for real-time communication (like video calls or chat functions) from working correctly. Wikipedia WebRTC
In Firefox Settings -> Privacy & Security, set DNS over HTTPS to
Default Protection or OFF, this enables Firefox to use RethinkDNS’s DNSCrypt
resolver or whatever you chose.
Checking for DNS Leaks
Go to:
https://dnsleaktest.com
Also crosscheck with:
https://ipleak.net
ipleak.net may show many more servers but as long as they are all related to
your resolver (i.e., WoodyNet for Quad9) you are not leaking to your ISP or
other third-parties.
For DNSCrypt with Quad9 Security, dnsleaktest found 5 servers all with the ISP
WoodyNet indicating success through Quad9. Quad9 relies on Packet Clearing
House, that’s where the WoodyNet name comes from.
When on mobile data, when going to https://dnsleaktest.com the results may
show more servers. As long as they are all the same ISP you’re good.
A different solution could be to experiment with more strict RethinkDNS settings and just use the browsers built-in DNS over HTTPS on max protection. Having more strict defaults for Rethink with all of your apps and configuring your browser separate may be a better option, the choice is yours.
When hunting down a solution you can go to Configure -> Logs, then try to
visit the site that wouldn’t work while watching the logs. You should see
Firefox pop up, click it, in the top right of the pop up should be the reason
it was blocked.
- PrivacyTests.org, tests different browsers in different privacy categories.
DuckDuckGo
I also tested DuckDuckGo with its stock configuration and dnsleaktest.com
showed that DDGs traffic was successfully tunneled through Rethink to Quad9s’
servers.
dnsleaktest.com showed all WoodyNet ISPs indicating success.
Chromium Based Browsers (Brave)
For Brave, if you want your DNS routed through Rethink you have to turn
Use secure DNS off.
I also had to set Brave to Bypass Universal
NOTE: Brave has been known to have some spyware in the past, though most of it can be opted out of.
More Fine Grained Control & Enhanced Privacy
In this section we will switch from a default allow to a default deny; blocking network access to every app. We will then go through and only enable networking for the apps that we need and trust.
✔️ Click to Expand Android Privacy Tips for the paranoid
If you don’t like the idea of someone forcing you to unlock your phone so they can sift through your data:
-
It’s often recommended to not use biometrics as they can be forcibly be taken from you while a password typically can’t.
-
Cellebrite relies on your phone being in AFU mode which is After first unlock.
BFU: Before First Unlock. This refers to the state right after a device restarts. Since you haven’t entered your password yet, the system has not unlocked the phone’s storage, so its contents remain encrypted.
AFU: After First Unlock. In this state, the device has been unlocked at least once since it was powered on, even if it is currently locked. The encryption keys are loaded into RAM, making the device’s stored data more accessible and increasing the potential attack surface compared to the BFU state.
-
Set the Auto optimization (Auto Restart) to the shortest amount of time possible. When your phone first reboots it is in BFU mode which is Before first unlock mode. Biometrics are usually disabled in this mode and the encryption keys are not saved in RAM making the attack surface much smaller.(i.e., they typically have to guess the password or brute-force it)
- That said, if someone gets your phone while it’s in AFU mode they will only have the amount of time until your next auto reboot to try to extract your data.
-
You can also try to reduce the number of unlock attempts before the device wipes. The default is typically 20 attempts, I haven’t found a way to lower this.
-
You can’t really trust Airplane mode on anything but GrapheneOS. If you are able to remove your battery, do that when it matters.
-
In your
About phonesection, name your device something that doesn’t expose anything useful about yourself. -
Set a pin for your phones sim card to prevent sim swaps.
-
Consider something like mint mobile or visible, they often let you register with only a spoofed email address.
Bluetooth is inherently insecure and broadcasts information about your device constantly. Walmart and other stores have started using Bluetooth to see hot zones to strategically place adds on the most traveled areas.
-
Turn off Wi-Fi, Bluetooth, Location, Nearby device scanning, etc. when not in use or needed. Your weather app doesn’t need your location 24/7, find one that lets you manually enter your location and time zone.
-
Use a password manager rather than using the one built into the browser, I like BitWarden for sync and KeepassDX for self storage. Since you don’t have to remember the passwords, have it generate long complex unique passwords for each app.
- You can use KeepassDX to store a complex password for Bitwarden that is kept 100% offline so your synced passwords are better protected.
In Settings -> Google in
Google services -> All services -> Privacy & security Select Ads and either
Reset advertising ID every so often, or Delete advertising ID but know the
consequences of the latter.
In the same All services area, click Usage & diagnostics to disable it.
Turn off Improve Location Accuracy
Go to your accounts Manage your Google Account, Data & privacy and disable
all the Activity tracking (Pause it or turn it off).
If you rely on Google services, in the Data & privacy section, it is helpful
to run a Privacy Checkup.
When you’re done, always go to Settings -> Accounts and backup and remove the
Google account.
I recommended that you remove Android System SafetyCore.
F-Droid
F-Droid is a free and open-source app store for Android that only distributes open-source software, without ads, proprietary code, or user tracking. All apps are vetted for privacy and transparency, making it a strong alternative to Google Play.
No registration or account is required, further protecting privacy.
Google restricts apps that remove Google services or modify their core features to disable their data collection strategies. This is why you won’t find many privacy by default browsers in Google Play. Browsers like IronFox and Cromite are solid options that disable telemetry and more that are available outside of the Play ecosystem.
I personally use IronFox as my default and have been happy with it so far. It comes with uBlock installed by default which I recommend that you learn how to get the most out of. With Firefox fingerprint protection, a few about:config tweaks, and uBlock you can accomplish what used to take 6 extensions to do. See: uBlock Origin
For more compartmentalization, Brave Browser now has an F-Droid repository that can be added.
Open F-Droid, in Settings click Repositories, click +, and for the Brave official repository add the following:
NOTE: If you don’t want to add a repo, you can use the FFUpdater app to install privacy friendly browsers including brave.
https://brave-browser-apk-release.s3.brave.com/fdroid/repo
Or you can visit https://brave-browser-apk-release.s3.brave.com/fdroid/repo/index.html and scan the QR code.
Another app that I recently discovered on F-Droid is the FFUpdater app. It enables you to install and update different privacy friendly browsers as well as updating FairEmail, K-9 Mail/Thunderbird, and Orbot in a privacy respecting way.
-
FairEmail is great, it has many privacy features including support for encryption,reformatting messages to prevent phishing, and more.
-
To install apps through FFUpdater tap the phone logo with the down arrow on the top right of the screen.
Aurora Store is an open-source client for Google Play that respects user privacy and lets you download and update proprietary apps with an anonymous account. You can use F-Droid for open-source apps and Aurora for proprietary apps.
I would recommend against Obtainium, the dev is a piece of shit and you can do anything it can do easily without it. Discoverium is a fork run by someone else, might be an option.
Another option I came across that I haven’t tried yet is Zapstore It’s a decentralized, permissionless app store that lets you support devs without PII.
Share Files and more anonymously:
Sharing Images:
- Clean metadata before sharing pics, I personally use Metadata Remover
SearXNG
While we’re on the topic of browsers, it’s worth considering a privacy-focused metasearch engine like SearXNG. SearXNG anonymizes your search queries and removes any identifying data before passing them along to Google or other search engines. This way, you can receive top-quality search results while keeping your privacy intact
To get started find a few local instances and others and either bookmark them for quick access or do what I do and add them to use as your default search engine.
In Firefox go to about:preferences#search, scroll to the bottom and click
Add. Name it whatever you want and for the URL add one of the local instances
followed by search?q=%s for example, for an instance in the US with A+
ratings:
https://searx.ankha.ac/search?q=%s
Then click Add Engine, and back near the top of the page you can set one
instance as your default search engine and another for private windows. I have
had a good experience so far using SearXNG with the occasional problem that is
solved by using a different instance.
❗️ Note: When using a public SearXNG instance from someone you don’t know, you are trusting the operator since they can see the IP address making the searches and might keep logs. The site https://searx.space/ provides helpful details to choose instances based on trust and location. If you want the highest level of privacy and full control, running your own instance is the best option—though it requires more setup. Using public instances is a great way to get started and improve privacy compared to regular search engines.
Browser compartmentalization is a technique where you use multiple browsers to separate different types of online activities. Instead of doing all browsing in a single browser, you dedicate specific browsers to distinct tasks, for example, one browser for email and banking, another for social media, and a third for casual web surfing. This separation makes it difficult for websites and trackers to link your activities across different contexts because cookies, trackers, and browsing data are isolated within each browser.
- AI and social media Apps often require extensive permissions and deep access to your device data and can run background processes that you can’t easily monitor. If you avoid the apps and only access AI and social media in your browser you get the benefits of browser sandboxing, tracking prevention, cookie controls and more. You gain more benefits by using the compartmentalization technique explained above, possibly one for AI and one for social media.
The following website lists the tracking protection mechanisms implemented by the major browsers and browser engines:
✔️ Click to Expand uBlock Guide
uBlock Origin
Arkenfox and Betterfox are JavaScript configuration files used to harden/control Firefox settings on a computer.
BetterFox does a great job explaining how to use uBlock with solid recommendations.
If you wanted to apply the
Privacy Essentials
list you would click
subscribe,
which launches the uBlock asset viewer where you can see all of the domains that
will be blocked before clicking Subscribe again to apply them.
Adding other lists
Click the uBlock logo, Settings, Filter lists, scroll to the bottom and choose Import…, Paste the url of your chosen list, and click Apply changes.
For example, Arkenfox suggests adding the Actually Legitimate URL Shortener Tool. Add https://raw.githubusercontent.com/DandelionSprout/adfilt/master/LegitimateURLShortener.txt to the Import… section and click Apply changes. If you scroll up, you’ll see that it was added and chosen.
Setup your Blocking mode
Many opt for medium mode. To do so you need to:
Settings pane:
- Open the dashboard, and choose I am an advanced user.
Filter lists pane:
-
All of uBO’s filter lists: checked
-
EasyList: checked
-
Peter Lowe’s Ad server list: checked
-
EasyPrivacy: checked
-
Online Malicious URL Blocklist: checked
My rules pane (4th pane from the left):
Underneath Temporary rules add, you literally type this in:
-
Add
* * 3p-script block -
Add
* * 3p-frame block -
Click
Save -
Click
<- Commit(Not required on phone) -
Setting medium mode on your phone is likely overkill because it’s not super quick to switch broken sites to easy mode as it is on a computer.
Fallback to easy mode
Click the uBlock logo while on the site you want to use easy mode on. You’ll see the power button to the top right, the Global rules and the Local rules which start 3 boxes from the left. Click the box under the Local rules for 3rd-party scripts. The box will change colors, dark gray = NOOP (No Operation). This tells uBO to ignore the aggressive global block for this site, and let the normal filter lists handle the blocking.
Click the padlock to make the rules NOOP rules permanent.
You can also disable JavaScript in Settings as needed replacing the need for NoScript.
Default Deny
If you read the following GrapheneOS discussion forum written by an RDNS dev:
The post suggests you go to Home -> Apps and right under Showing all apps
click on the grayed out 🛜📶 to set a rule to block both Metered and Unmetered
connections to all apps by default. This will block both Wi-Fi and mobile data
connections to all apps on your device.
The point here is that not every App on your device needs network access all the time or at all in some cases.
Search for the apps you use/trust and:
-
start by enabling Wi-Fi 🛜 and mobile-data 📶 as well as Bypass Universal so your required apps aren’t blocked right when the screen shuts off or affected by the firewall.
-
Next, check your logs and see why it’s being blocked. Look at the domains involved and set trust rules for said domains. If it is unclear why networking still isn’t working, you can:
-
If you’re using Rethink’s DNS, you can try allowing the app to
Bypass DNS & Firewall. Try the app again, does it work? If not: -
You can also
Isolatean App, you then have to set up trust | allow rules for domains or IPs over a period of time which can take a while. -
Excludethe app. This makes RethinkDNS completely unaware of the app and is often what is required for Reddit. It is my understanding that after youExcludean App, your systems Automatic Secure DNS will pick it up.
A few apps that typically need network access on Android:
-
Google Play services (
com.google.android.gms): push notifications, safety checks, etc. -
Google Play Store (
com.android.vending) app updates and downloads -
Android System (usually UID 1000, often
androidorsystem): Core OS connectivity checks, NTP time sync, and network management. Blocking can cause “no internet” errors. -
Android System WebView (
com.google.android.webview): Renders web contents in apps. -
Download Manager (
com.android.providers.downloads): Manages file downloads from apps/browser. Without it, downloads stall. -
Media Storage
-
Settings
Tor
Tor itself is a network and software that anonymizes traffic by encrypting it and bouncing it though a series of volunteer-operated relays around the world before reaching the destination. Orbot uses this Tor network to encrypt and anonymize internet traffic for Android apps.
Orbot routes app traffic through Tor but doesn’t provide the same kind of traffic isolation and sandboxing that Tor browser offers on desktop. Be aware of the limitations and use Tor Browser on a hardened system or specialized software such as Tails or Whonix when anonymity truly matters.
If you want to learn how Tor works, I suggest reading the following in this order:
Tor is at risk, and needs our help. Despite its strength and history, Tor isn’t safe from the same attacks oppressive regimes and misinformed legislators direct at encryption and many other privacy-enhancing technologies.–How to Support Tor
✔️ Click to Expand Tor Section
❗️ WARNING: Tor gets much of its funding from the US government and is not immune to vulnerabilities or misconfigurations.Tor Browser is not the most secure browser, anonymity and security can often be at odds with each other. Having the exact same browser as many other people isn’t the best security practice, but it is great for anonymity. Tor is also based on Firefox Esr, which only receives patches for vulnerabilities considered Critical or High which can be taken advantage of.
Tor Project has also done some sketchy/questionable things lately such as removing user agent spoofing which made every user appear as if they were using the same version of Windows. This decision reduces anonymity and is questionable at best, seems to me there ought to be a very good reason for doing this which I can’t find. Tor is still one of the best anonymity solutions, just stay informed and cautious.
The following is a summary of some of the Tor Overview, all credit goes to them. It is important to spread the word when you can!
If you are fortunate to live outside of oppressive regimes with extreme censorship, using Tor for every day, mundane activities is likely safe and won’t put you on any harmful “list.” Even if it did, you’d be in good company, these lists mostly contain great people working tirelessly to defend human rights and online privacy worldwide.
By using Tor regularly for ordinary browsing, you help strengthen the network, making it more robust and anonymous for everyone. This collective support makes staying private easier for activists, journalists, and anyone facing online surveillance or censorship. The writer of the PrivacyGuides article mentions using Tor when he needs to access Google Maps to protect his privacy
So, consider embracing Tor not only for sensitive browsing but also for daily routine tasks. Every user adds valuable noise to the network, helping protect privacy and freedom for all.
Setting up Orbot with a TCP-only Proxy

- Helpful guide, What is Orbot?
TCP-Only Proxies forward all TCP-level connections from selected apps to Orbot.
TCP-Only Proxies work best for Apps that use multiple TCP protocols beyond just basic web browsing (HTTP/HTTPS) like messaging apps (Signal), search apps (DDG), etc. Because it proxies all TCP traffic, it can cause some apps to slow down or break if they expect direct DNS or UDP.
First install Orbot, Open Orbot -> More -> Orbot Settings and turn on
Power User Mode. This is important, if you forget this Rethinks auto Orbot
will not let you choose between SOCKS and HTTP proxies.
You should also check Allow Background Starts ON.
In Configure -> Proxy -> Setup Orbot:
-
Click
Add / Remove 0 apps, search for an app that you want to run through Orbot. For simple testing I chose DuckDuckGo with a TCP-only Proxy. -
In
Home -> Appssearch forOrbotand setOrbot -> Bypass UniversalON -
On the first time starting Orbot through Rethink, you’ll have to click the
Configure -> Proxy -> Setup Orbot -> Orbot>toConnectas well as grant initial permissions. After you start Orbot successfully, check out RethinksHomeand below the STOP button should sayProtected With Tor.
Open DuckDuckGo and go to:
https://dnsleaktest.com
# CrossCheck
https://ipcheck.net
❗ You may see that ipleaktest initially shows a Tor exit relay location such as the Netherlands, once you complete a Standard Test, it still shows WoodyNet ISPs. Since I configured Rethink to use DNSCrypt with Quad9 this is completely expected. This confirms that my DNS traffic is not leaking to my ISP and is properly anonymized through Tor and Quad9. As long as you don’t see your actual ISP’s servers in the results, your setup is working as intended.
Now you can add more apps that would benefit from anonymity such as FairEmail, RSS feeds, and crypto wallets. I believe for Signal, it requires that you to set up the SOCKS5 proxy to work correctly which is pretty straightforward.
Look into an RSS Feed, they give you complete control of the content you consume, no algorithm involved!
This can also be useful on public Wi-Fi or other insecure networks.
-
You can also open Orbot and
Choose How to Connect, if you want to hide Tor use. -
If you live in an area where Tor use isn’t discriminated against, consider Activating your Orbot
Kindnesstab so others that are in oppressive regimes can use your device as a bridge. This is a great way to give back!
Setting up a SOCKS5 Proxy
If you have Orbot set up through auto mode, you’ll have to disable it.
Open Orbot -> More: Near the bottom of the screen you’ll see HTTP: 8118, and
SOCKS: 9050, these are the Port numbers. We will compare these to Rethinks
defaults. (They match).
Back in Rethink, Configure -> Proxy -> Setup SOCKS5 Proxy.
In the App dropdown choose Orbot.
-
Hostname:
127.0.0.1 -
Port Number:
9050 -
Leave the rest of the defaults and Hit
Set -
Go
Home, below the STOP button you should seeProtected With SOCKS Proxy. Now all of your devices traffic that doesn’t bypass Rethink is routed through the SOCKS5 proxy. -
In
Configure -> DNSand turnNever proxy DNSON -
Open your browser and visit
https://dnsleaktest.com, your public IP should no longer be your ISPs. -
SOCKS5 alone doesn’t encrypt the traffic; it only proxies or routes it. Orbot uses SOCKS5 to let apps route traffic into the Tor network. Once inside the Tor network, the traffic is encrypted in layers.
-
There is a misconception that Orbot is a “free VPN”. It’s actually part of an anonymity network designed to hide your identity by sending your traffic through multiple servers. And the SOCKS5 proxy that Orbot uses isn’t a VPN either, it simply directs certain app traffic through a proxy server without creating a full encrypted tunnel from your device like a VPN does.
WireGuard/VPN
Keep your goals in mind before paying for a VPN, some allow you to pay with cash anonymously such as Mullvad VPN and a few others. Remember that you are transferring trust, from your ISP to the VPN provider.
Configure -> Proxy -> Setup WireGuard -> +:
With the WireGuard protocol, the provider usually gives you either a QR Code, or
a configuration file (e.g., .conf file) which is a plaintext file that
contains what is needed. You download that file and Click IMPORT which brings
up your phones filesystem.
Alternatively, the CREATE option for advanced users who are setting up their
own WireGuard network, typically if you host your own VPN server.
After this is setup you should always verify that your traffic is encrypted (hidden IP) and your DNS queries are protected (no leaks).
You could:
-
First check what your IP address is before enabling WireGuard at
whatismyipaddress.comoripleak.net, take note of your Public IP, ISP, and City/Country this is your real info that the VPN needs to hide. -
Connect to your Wireguard/VPN server. Choose the server location you want and go back to check again.
-
Check for leaks, go to
dnsleaktest.comand run a Standard test. If you don’t see your Public IP listed anywhere, you don’t have a leak.
Fixing DNS Leaks:
-
Use WireGuard in Simple mode.
-
Or, use
Configure -> DNS -> Split DNSwith WireGuard inAdvanved + Lockdownmode (if running on Android 12+)
To ensure your DNS requests go through your VPN, check
Configure -> Logs -> DNS. Tap on the entries that appear there, which should
bring up a bottomsheet with more info about the request and how it was handled.
Requests handled by WireGuard are shown with sub-text “resolved x min ago by
wg…” –celzero
Proton VPN
This works for ProtonVPN Free as well.
Written by celzero Here
-
On your Android, visit https://account.protonvpn.com/downloads
-
Scroll to WireGuard
-
Give it a name
-
Select Android
-
Enable VPN options as needed.
-
Press Create or tap on Download.
- Open Rethink, goto Configure -> Proxy -> Setup WireGuard
-
Tap on the “+” button
-
Choose Import
-
Select the downloaded configuration file
-
If the import is successful, you should see a new profile prefixed wg added to the list.
-
Tap on that
wgentry.
-
In the new screen that opens up, tap on Add / Remove applications.
-
Select apps that should be part of this particular WireGuard profile.
-
Go back.
- Enable / disable that wg profile as desirable.
- On my phone, when I saved the VPN profile, it automatically added
.txtto the end of the file causing the import to fail. I was able to open my phones documents and long hold the file to rename it, afterwards I was able import it.
SIMPLE
When you enable: Configure -> Proxy -> Setup WireGuard - SIMPLE
All connections from all apps, including DNS, will be routed to a single, active WireGuard VPN. Other active WireGuard VPNs, if any, will be stopped.
If you tap SIMPLE, you can see the name of your VPN, check your Logs, and see your Peer connection.(your device that participates in the VPN network)
ADVANCED
Same as above but tap ADVANCED before enabling the toggle. This lets you
choose which apps will be routed through the VPN while the rest are routed as
usual.
This Mode also lets you enable:
-
Lockdown: Selected apps will only be routed through this WireGuard VPN, regardless of whether the VPN is active or disabled.
-
Always-on: All connections and apps not routed by other WireGuard VPNs will be routed by this one.
-
Mobile only: Use this WireGuard exclusively over mobile data.
After you configure your Advanced settings as needed, enable the toggle.
Always make sure to watch your Logs as well as test with something like <dnsleaktest.com>, you shouldn’t see your actual Public IP anywhere. It’s advisable to check for WebRTC Leaks as well if you have that enabled.
Troubleshooting VPN issues
There are a few Proxy settings in Configure -> Network -> PROXY, where you
can:
-
Enable
Do not randomize WireGuard listen port: Keep WireGuard listen ports fixed (no randomization) in Advanced mode. -
Enable
Endpoint-Independent mapping: When enabled, UDP sockets maintain a fixed address and port for all destinations.
Check your Logs
In Configure -> Proxy -> Setup WireGuard, click on your VPN which will give
you access to the LOGS.
Click the top entry, it will bring up the IP address and domain and show your rules the app. You can click the dropdown menu’s to set new Universal Firewall rules, as well as block/trust rules for IPs and domains.
Logs
On-device logging is on by default. You can find it in Configure -> Settings.
From there, you can set the log level and choose a notification action.
If anyone else uses your phone, it’s probably a good idea to enable app lock.
Go to Configure -> Logs, and try to access the app that’s not working. You
should see said app at the top of the Network Logs, click it. In the top right
of the tab, you’ll see the reason why it’s not working such as: App Blocked,
or DNS Bypass.
This DNS Bypass means that the App in question is trying to bypass the Rethink
Tunnel and being actively blocked. You can search for said app and try setting
IP or Port Trust rules.
You can also go to Home -> Apps and search for the App you need, click on it
and at the bottom of the screen you will see IP Logs, and Domain Logs.
Once you click on the log of the app in question, you’ll be given 3 drop down options. If you set an app to Bypass DNS and Firewall settings, you will see that in the first dropdown box.
The next drop down is Block,trust this IP for this app where you can set a
rule to Block or Trust.
Resources
✔️ Click to Expand Resources
Things change frequently in the privacy/security relm, many sites provide an RSS feed that automatically updates when they post new content. I’ve listed a few feeds that are good to follow IMO.
RSS (Really Simple Syndication) is a technology that delivers automatic updates from your favorite websites, blogs, or podcasts in a standardized format. It saves you time by eliminating the need to manually check for new content. Best of all, your feed includes only what you choose—no algorithms deciding what you see.
I personally use Feeder as my RSS reader, it’s open-source and has been great IMO.
RSS Feeds:
With Feeder, you click the 3 dots in the top right and click + Add feed, you can then type the regular address of a site & if it has RSS feeds, Feeder will list them. Most sites that have an RSS feed will have an orange RSS button that you can click on to get the address.
-
PrivacyTools: https://www.privacytools.io/guides/rss.xml
-
Darknet Diaries: https://podcast.darknetdiaries.com/
-
Hacker News: https://news.ycombinator.com/rss
-
PrivacyGuides: https://privacyguides.org/rss/
-
Tor Project blog: https://blog.torproject.org/feed.xml
-
Mozilla Security Blog: https://blog.mozilla.org/security/feed/
-
F-Droid Forum - Latest posts: https://forum.f-droid.org/posts.rss
-
Opensource.com: https://opensource.com/feed
-
DuckDuckGo Blog: https://spreadprivacy.com/rss/
-
Electronic Frontier Foundation Deeplinks Blog: https://www.eff.org/rss/updates.xml
-
KrebsonSecurity: https://krebsonsecurity.com/feed/
-
Arkenfox recent commits: https://github.com/arkenfox/user.js/commits/master.atom
-
Arkenfox release notes: https://github.com/arkenfox/user.js/releases.atom
-
Brave Blog: https://brave.com/blog/index.xml
-
Ars Technica: https://feeds.arstechnica.com/arstechnica/index/
-
PrivacySavvy: https://privacysavvy.com/feed/
- PrivacySavvy Security Archive: https://privacysavvy.com/security/feed/
-
Orbot is a free app from the Guardian Project that empowers other apps on your device to use the internet more securely. Orbot uses Tor to encrypt your internet traffic and hide it by bouncing through a seris of computers around the world. –TorProject Orbot
-
WireGuard is an extremely simple yet fast and modern VPN that utilizes state-of-the-art cryptography. –Wireguard.com
"goodbye ${ { d = "world";}.d}"
Encrypted Arch Install for UEFI Systems with UKI and Secure Boot
✔️ Click to Expand Table of Contents
The ultimate installation resource is always goint to be the:
-
Verify PGP Signatures, the ISO directory is just the same directory where your Arch Linux ISO file is stored.
Always review the Wiki and never just plug random things suggested by me or anyone else without first doing your own research.
Edited: 09-25-25 Removed encrypted swap section. Big usability downside IMO.
✔️ Verifying Arch Linux ISO on Other Distributions
❗ NOTE: If you only want to verify the ISO once, you can temporarily import the public key, verify the signature, and then you don’t need to keep the key permanently in your keyring or sign it locally. This example is from the last release, but the process is the same.
With sequoia-sq, you can get the Arch release signing key with:
sq network wkd search pierre@archlinux.org --output release-key.pgp
Export the chosen key to a .pgp file:
sq cert export --keyring=release-key.pgp --cert=3E80CA1A8B89F69CBA57D98A76A5EF9054449A5C > pierre-archlinux.pgp
Import into your keychain:
gpg --import pierre-archlinux.pgp
gpg: key 0x76A5EF9054449A5C: 9 signatures not checked due to missing keys
gpg: key 0x76A5EF9054449A5C: public key "Pierre Schmitz <pierre@archlinux.org>" imported
gpg: Total number processed: 1
gpg: imported: 1
gpg: marginals needed: 3 completes needed: 1 trust model: pgp
gpg: depth: 0 valid: 3 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 3u
gpg: next trustdb check due at 2026-08-23
- Now, you should see
<pierre@archlinux.org>and his keys when you rungpg --list-keys
Finally, verify the signature:
sq verify --signer-file release-key.pgp --signature-file archlinux-2025.08.01-x86_64.iso.sig archlinux-2025.08.01-x86_64.iso
Authenticated signature made by 3E80CA1A8B89F69CBA57D98A76A5EF9054449A5C (Pierre Schmitz <pierre@archlinux.org>)
1 authenticated signature.
❗ To ensure the key is authentic and not spoofed, verify that the key fingerprint matches the official Arch Linux signing key fingerprint, which can is linked below and on the Arch website.
This shows that the signature was made by the key with the ID
3E80CA1A8B89F69CBA57D98A76A5EF9054449A5C (Pierre Schmitz)
You can check the keys fingerprint with:
gpg --fingerprint 3E80CA1A8B89F69CBA57D98A76A5EF9054449A5C
- Verify it against the Arch Linux master-keys
Verify it against the Arch Linux master-keys
With the sq verify command GPG authenticated that the signature is valid and
that the key used to sign is trusted in our keyring.
1 authenticated signature confirms the files integrity and authenticity.
We have successfully verified that the file was signed by Pierr’s official Arch Linux key and has not been tampered with.
The following is only if you currently already have keys on your gpg keyring.
☑️ Click to expand Key Signing and Publishing Example
List your keys to get the arch keyID:
gpg --list-keys
# ... snip ...
pub ed25519/0x76A5EF9054449A5C 2022-10-31 [SC] [expires: 2037-10-27]
Key fingerprint = 3E80 CA1A 8B89 F69C BA57 D98A 76A5 EF90 5444 9A5C
uid [ full ] Pierre Schmitz <pierre@archlinux.org>
uid [ full ] Pierre Schmitz <pierre@archlinux.de>
sub ed25519/0xD6D13C45BFCFBAFD 2022-10-31 [A] [expires: 2037-10-27]
sub cv25519/0x7F56ADE50CA3D899 2022-10-31 [E] [expires: 2037-10-27]
Sign the key:
gpg --sign-key 0x76A5EF9054449A5C
Now you can export and publish the new public key and send it to a keyserver:
gpg --export --armor 0x76A5EF9054449A5C > archlinux-signed.asc
gpg --send-keys 0x76A5EF9054449A5C
The more people that verify, sign, and re-export and publish their keys the better for the web of trust that gpg uses making the network more secure for everyone.
- Connect to Wi-Fi:
iwctl
[iwd]# device list
[iwd]# station wlan0 scan
[iwd]# station wlan0 connect NETGEAR80
# Enter your Password
# Check Connection
[iwd]# station wlan0 show
[iwd]# exit
ping -c 3 archlinux.org
- Update package databases and mirrorlist:
pacman -Syyu
Save a backup of your current mirrorlist so we can safely update it:
cp /etc/pacman.d/mirrorlist /etc/pacman.d/mirrorlist.bak
pacman -S reflector
reflector --list-countries
# Example if you live in the US
reflector -c US --protocol https --age 6 --fastest 5 --sort rate --save /etc/pacman.d/mirrorlist
This actually improves security by only providing HTTPS mirrors, by default
both HTTP and HTTPS are used.
NOTE: This can take a bit and you can expect some failures..
- Set keyboard layout, font, and system clock:
Default keymap is US, if you need something different:
localectl list-keymaps
loadkeys <chosen-map>
# Increase font size
setfont ter-132b
sudo pacman -S chrony
/etc/chrony.conf:
# Copyright © 2014-2025 GrapheneOS
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
server time.cloudflare.com iburst nts
server ntppool1.time.nl iburst nts
server nts.netnod.se iburst nts
server ptbtime1.ptb.de iburst nts
server time.dfm.dk iburst nts
server time.cifelli.xyz iburst nts
minsources 3
authselectmode require
# EF
dscp 46
driftfile /var/lib/chrony/drift
dumpdir /var/lib/chrony
ntsdumpdir /var/lib/chrony
leapseclist /usr/share/zoneinfo/leap-seconds.list
makestep 1.0 3
rtconutc
rtcsync
cmdport 0
noclientlog
The above setup uses Network Time Security (NTS) and is a more modern safer way to synchronize time using chrony.
sudo systemctl disable --now systemd-timesyncd
sudo systemctl enable --now chronyd
timedatectl set-ntp true
timedatectl list-timezones
# Example
sudo timedatectl set-timezone America/New_York
- Partition your Disk:
The only required partitions are one for the root directory / (typically 1G)
and one for booting in UEFI mode, an EFI system partition (typically the rest of
the space left).
In this section, I’ll assume that you’re starting with a clean slate. If you’re not, you can use your existing partitions if they meet the above requirements or delete them.
To delete existing partitions with cfdisk, you launch it on the target
filesystem, select the partition you want to delete and Select -> [ Delete ]
until all you see is Free space and a clean slate and [ Write ] & [ Quit ]
to save your changes.
Identify your target disk (e.g., /dev/nvme0n1):
fdisk -l
cfdisk /dev/nvme0n1
-
Select ->
[ Free Space ]-
Select ->
[ New ] -
Partition size:
1G -
Type ->
EFI System
-
-
Select ->
[ Free Space ]-
Partition size: The rest of the space
-
Type ->
Linux filesystem -
Select ->
[ Write ] -
Select ->
[ Quit ]
-
- Format the EFI partition as FAT32:
mkfs.fat -F32 /dev/nvme0n1p1
Leave the root partition unformatted for the next step.
- Encrypt the Root partition and Open it:
cryptsetup luksFormat /dev/nvme0n1p2
cryptsetup open /dev/nvme0n1p2 cryptroot
Create a filesystem:
mkfs.btrfs /dev/mapper/cryptroot
If you mount the filesystem with compression before running genfstab, it will
automatically add the chosen compression to the fstab.
mount /dev/nvme0n1p1 /mnt/boot # Mount EFI partition at /mnt/boot
mount -o compress=zstd /dev/mapper/cryptroot /mnt
Install the Base System on /mnt with pacstrap
If you prefer Nvim or a hardened kernel, change it here.
pacstrap -K /mnt base linux-zen linux-zen-headers linux-firmware networkmanager helix lightdm lightdm-gtk-greeter btrfs-progs cryptsetup sudo base-devel efibootmgr systemd-ukify
- Generate the Filesystem Table:
The EFI and root partitions need to be mounted before you generate the filesystem table in the next step.
genfstab -U /mnt >> /mnt/etc/fstab
#
hx /mnt/etc/fstab
cat /mnt/etc/fstab
Fix security hole and harden /boot permissions by adding
fmask=0137, dmask=0027:
hx /mnt/etc/fstab
Example
# Static information about the filesystems.
# See fstab(5) for details.
# <file system> <dir> <type> <options> <dump> <pass>
# /dev/mapper/cryptroot
UUID=6d68a2bf-34ea-4adc-86d5-cb1d56de44a4 / btrfs rw,relatime,compress=zstd:3,ssd,space_cache=v2,subvol=/ 0 0
# /dev/nvme0n1p1
UUID=B88B-844F /boot vfat rw,relatime,fmask=0137,dmask=0027,codepage=437,iocharset=ascii,shortname=mixed,utf8,errors=remount-ro 0 2
chmod 700 /boot
chmod 600 /boot/loader/random-seed
Important: The fstab should list both partitions, if it doesn’t you’ll
need to ensure both partitions are mounted and regenerate your fstab again with
genfstab. Also ensure that the root partition was mounted with compression.
- Change Root (
chroot) into the New Installation
arch-chroot /mnt
Create a user:
useradd -m -G wheel -s /bin/bash yourusername
passwd yourusername
Create an admin password:
passwd
Enable sudo for wheel group:
In /etc/sudoers uncomment the line:
%wheel ALL=(ALL:All) ALL
- Edit
/etc/mkinitcpio.confin the new system to add ansd-encrypthook before thefilesystemsattribute.
-
Locate the
HOOKSline -
Insert
encryptbefore filesystems
vim /etc/mkinitcpio.conf
To use sd-encrypt, it is necessary to replace udev with systemd and if you
require a different keyboard layout from US sd-vconsole is needed.
# mkinitcpio.conf
# *IF* you use an nvme drive
MODULES=(nvme)
FILES=(/etc/crypttab.initramfs /etc/vconsole.conf)
# ... snip ...
HOOKS=(base systemd autodetect microcode modconf kms keyboard keymap consolefont sd-vconsole block sd-encrypt filesystems fsck systemd-ukify)
# ... snip ...
Create /etc/crypttab.initramfs:
cryptroot UUID=your-uuid none luks,discard
Create /etc/vconsole.conf:
For example KEYMAP=fr is french
KEYMAP=us
FONT=lat9w-16
Edit /etc/mkinitcpio.d/linux-zen.preset, this enables mkinitcpio -P to
generate a UKI in /boot/EFI/Linux/:
# mkinitcpio preset file for the 'linux-zen' package
ALL_config="/etc/mkinitcpio.conf"
ALL_kver="/boot/vmlinuz-linux-zen"
PRESETS=('default' 'fallback')
# PRESETS=('default')
# default_config="/etc/mkinitcpio.conf"
# default_image="/boot/initramfs-linux-zen.img"
default_uki="/boot/EFI/Linux/arch-linux-zen.efi"
default_options="--splash /usr/share/systemd/bootctl/splash-arch.bmp"
#fallback_config="/etc/mkinitcpio.conf"
#fallback_image="/boot/initramfs-linux-zen-fallback.img"
fallback_uki="/boot/EFI/Linux/arch-linux-zen-fallback.efi"
fallback_options="-S autodetect"
Generate the initial RAM Filesystem (initramfs) image.
mkinitcpio -P
❗️ TIP: If
mkinitcpio -Pfails, exitarch-chrootand ensure both the boot & root partitions are mounted.arch-chrootback into/mntand try again. Ensure that/boot/EFI/Linux/arch-linux-zen.efiand/boot/EFI/Linux/arch-linux-zen-fallback.efiexist for your kernel of choice.
- Install systemd-boot and setup Unified Kernel Image, (while still in chroot environment):
Ensure your UEFI variables are accessible:
efivar --list
This command won’t work if both partitions aren’t mounted, ensure they are both
mounted and if it still doesn’t work you can use --esp-path=/boot or
--esp-path=/mnt/boot.
bootctl install
Edit /etc/kernel/cmdline and add the output of blkid on your root partition.
For example:
blkid /dev/nvme0n1p2 > /tmp/uuid.txt
When you open /etc/kernel/cmdline in vim or helix, run :r /tmp/uuid.txt to
read the UUID of your root partition into the file. You only need the numbers
like so:
rd.luks.name=7b78e942-f4f7-4dde-a015-3a816305483f=cryptroot root=/dev/mapper/cryptroot rw
In the above example, 7b78e942-f4f7-4dde-a015-3a816305483f is the UUID
extracted from the blkid command.
Create a /boot/loader/entries/arch.conf with the following:
title Arch Linux
linux /vmlinuz-linux-zen
initrd /initramfs-linux-zen.img
options rd.luks.name=7b78e942-f4f7-4dde-a015-3a816305483f=cryptroot root=/dev/mapper/cryptroot rw quiet
❗️ NOTE: The
arch.confhere is redundant becausesystemd-bootis configured to autodetect the UKI. It doesn’t hurt to add it though and can prevent issues in the future. Ensurerd.luks.name=contains the UUID from the encrypted root partition. (e.g.,blkid /dev/nvme0n1p2).
And finally, a /boot/loader/loader.conf:
default arch.conf
# default @saved # return to last choice
timeout 4
console-mode auto
auto-entries 1
bootctl status
Set a password to protect systemd-boot with systemd-boot-password:
paru -S systemd-boot-password
sudo sbpctl install /boot
You will now be prompted for your password before you can edit kernel parameters.
systemd-ukify and the Unified Kernel Image:
We already added systemd-ukify in the pacstrap command, let’s add a few more
tools for this process:
sudo pacman -S sbsigntools efitools
Copy the existing template to /etc/kernel/uki.conf:
cp /usr/lib/kernel/uki.conf /etc/kernel/uki.conf
Edit /etc/kernel/uki.conf:
[UKI]
#Initrd=
#Microcode=
#Splash=
#PCRPKey=
#PCRBanks=
#SecureBootSigningTool=
SecureBootPrivateKey=/etc/kernel/secure-boot-private-key.pem
SecureBootCertificate=/etc/kernel/secure-boot-certificate.pem
#SecureBootCertificateDir=
#SecureBootCertificateName=
#SecureBootCertificateValidity=
#SigningEngine=
SignKernel=yes
Generate your signing keys:
ukify genkey --config /etc/kernel/uki.conf
The above command creates /etc/kernel/secure-boot-certificate.pem and
/etc/kernel/secure-boot-private-key.pem.
Building the UKIs:
mkdir -p /boot/EFI/Linux
mkinitcpio -P
# Output
Wrote signed /boot/EFI/Linux/arch-linux-zen.efi
==> Unified kernel image generation successful
Sign the boot loader with the new keys:
/usr/lib/systemd/systemd-sbsign sign \
--private-key /etc/kernel/secure-boot-private-key.pem \
--certificate /etc/kernel/secure-boot-certificate.pem \
--output /usr/lib/systemd/boot/efi/systemd-bootx64.efi.signed \
/usr/lib/systemd/boot/efi/systemd-bootx64.efi
Output:
Wrote signed PE binary to /usr/lib/systemd/boot/efi/systemd-bootx64.efi.signed
sudo bootctl install --secure-boot-auto-enroll yes \
--certificate /etc/kernel/secure-boot-certificate.pem \
--private-key /etc/kernel/secure-boot-private-key.pem
Output:
Copied "/usr/lib/systemd/boot/efi/systemd-bootx64.efi.signed" to "/boot/EFI/systemd/systemd-bootx64.efi".
3 Copied "/usr/lib/systemd/boot/efi/systemd-bootx64.efi.signed" to "/boot/EFI/BOOT/BOOTX64.EFI".
4 ⚠️ Mount point '/boot' which backs the random seed file is world accessible, which is a security hole! ⚠️
5 ⚠️ Random seed file '/boot/loader/random-seed' is world accessible, which is a security hole! ⚠️
6 Random seed file /boot/loader/random-seed successfully refreshed (32 bytes).
7 Secure boot auto-enrollment file /boot/loader/keys/auto/PK.auth successfully written.
8 Secure boot auto-enrollment file /boot/loader/keys/auto/KEK.auth successfully written.
9 Secure boot auto-enrollment file /boot/loader/keys/auto/db.auth successfully written.
10 Created EFI boot entry "Linux Boot Manager".
Fix the security hole:
chmod 700 /boot
Add the following to your /boot/loader/loader.conf:
secure-boot-enroll force
Reboot into setup-mode and it will start an automatic countdown where it enrolls your keys when the time runs out.
After successful key enrollment, reboot into UEFI again, enable Secure Boot and reboot.
When the desktop launches, check the output of bootctl status to ensure
Secure Boot: enabled (user)
If your system doesn’t accept them, you may have to do some conversions. You can also add the keys manually which is what I did.
✔️ Click to Expand conversion Examples
# Create workspace for certs and outputs
mkdir -p ~/secureboot/output
# Define certificate location
CERT_PEM="/etc/kernel/secure-boot-certificate.pem"
# Convert PEM cert to DER format for UEFI enrollment
sudo openssl x509 -in $CERT_PEM -out ~/secureboot/output/db.cer -outform DER
# Convert PEM cert to EFI Signature List
cert-to-efi-sig-list $CERT_PEM ~/secureboot/output/db.esl
# Sign the Signature List (run from wherever your private key is; do not copy it)
sudo sign-efi-sig-list -k /etc/kernel/secure-boot-private-key.pem -c $CERT_PEM db \
~/secureboot/output/db.esl ~/secureboot/output/db.auth
Manual Key Enrollment:
-
db.cer-> enroll in db (Trusted Signatures), Choose the desired drive, find the file and add it. Save as anAuthorized Signatureinstead ofPublic Key -
PK.cer-> enroll inPlatform Key(PK) -
Keep your
secure-boot-private-key.pemprivate, with the above commands we only use it when signing, and otherwise keep it protected.
- Enable LightDM and NetworkManager
systemctl enable lightdm
systemctl enable NetworkManager
❗️ NOTE: If you’re ever unable to run the above commands in the chroot environment, exit chroot and run:
systemctl --root=/mnt enable lightdm systemctl --root=/mnt enable NetworkManager
Configure LightDM greeter, edit /etc/lightdm/lightdm.conf to add:
# lightdm.conf
[Seat:*]
greeter-session=lightdm-gtk-greeter
Exit arch-chroot with exit
Unmount your partitions and reboot:
umount /mnt/boot
umount /mnt
cryptsetup close cryptroot
- Reboot
bootctl status
System:
Firmware: UEFI 2.70 (American Megatrends)
Firmware Arch: x64
Secure Boot: enabled (user)
TPM2 Support: yes
Measured UKI: yes
Boot into FW: supported
Creating a readonly snapshot of your root subvolume:
✔️ Click to Expand snapshot & Backup Example
- Create readonly snapshot of root subvol:
sudo btrfs subvolume snapshot -r / /root-snapshot-$(date +%Y%m%d%H%M%S)
- Mount your external backup disk:
sudo mkdir -p /mnt/backup
sudo mount /dev/sdc1 /mnt/backup
- Use rsync to copy the snapshot to the external drive:
List the available snapshots and get the exact name:
sudo btrfs subvolume list /
sudo rsync -aAXv --delete --progress /root-snapshot-20251002135000/ /mnt/backup/root-backup/
- Unmount the external drive:
sudo umount /mnt/backup
- Optionally, delete older snapshots to save space:
sudo btrfs subvolume delete /root-snapshot-OLD_TIMESTAMP
Restoring from a Backup
For this, you mount the partitions but don’t need to arch-chroot in.
-
Boot from a live USB
-
Mount your encrypted root partition (unlock if encrypted) and the external backup drive.
-
Move or delete the corrupted data on the root partition.
-
Use rsync to copy the backed-up snapshot from the external disk back to the root partition, preserving permissions and attributes.
sudo rsync -aAXv --delete /mnt/backup/root-backup/ /
Or, the Wiki’s suggestion, (Much more efficient, Direct backup of a running system without the need for btrfs snapshots):
sudo rsync -aAXHv --exclude='/dev/*' --exclude='/proc/*' --exclude='/sys/*' --exclude='/tmp/*' --exclude='/run/*' --exclude='/mnt/*' --exclude='/media/*' --exclude='/lost+found/' / /path/to/backup
-
/mnt/backup/root-backup/is the path where the backup snapshot is mounted on the external disk. -
/is the root partition mount point where you want to restore the files. -
The options:
-
-aarchive mode to preserve symbolic links, permissions, timestamps, etc. -
-Apreserve ACLs (access control lists). -
-Xpreserve extended attributes. -
-vverbose output. -
--deletedeletes files on the destination that don’t exist in the source, keeping an exact mirror.
-
If you want to restore a specific snapshot directory (e.g.,
/root-snapshot-20251002135000), replace the source path accordingly, like:
sudo rsync -aAXv --delete /mnt/backup/root-snapshot-20251002135000/ /
-
Reinstall the bootloader if necessary.
-
Unmount everything and reboot.
Automated backup
Create /etc/cron.daily/backup:
#!/bin/sh
rsync -a --delete --quiet /path/to/backup /location/of/backup
Change /path/to/backup to what needs to be backed-up such as /home or /
arch-chroot
✔️ Click to Expand `arch-chroot` Example
Say you forgot something, like forgetting to add a user and password. You reboot and go to TTY into your system and are hit with a AHHH I can’t log in WTF!
It’s as easy as repeating some of the steps above. Reboot into the Live
environment, remount your partitions and arch-chroot back in:
Open the encrypted root partition:
cryptsetup open /dev/nvme0n1p2 cryptroot
Mount the decrypted root:
mount /dev/mapper/cryptroot /mnt
Mount the EFI partition:
mount /dev/nvme0n1p1 /mnt/boot
Chroot into your installed system:
arch-chroot /mnt
useradd -m -G wheel -s /bin/bash yourusername passwd yourusername
- The
-s /bin/bashsets your default shell, you can use zsh if you have it installed.
Uncomment the line %wheel ALL=(ALL:All) ALL in /etc/sudoers
Exit chroot:
exit
Unmount and close LUKS:
umount /mnt/boot
umount /mnt
cryptsetup close cryptroot
reboot
Resources
✔️ Click to Expand Resources
Encrypted DNS ArchLinux with dnscrypt-proxy & dnsmasq
✔️ Click to Expand Table of Contents
❗ NOTE: There are many other ways for someone monitoring your traffic to see what domain you looked up via DNS that it’s effectiveness is questionable without also using Tor or a VPN. Encrypted DNS will not help you hide any of your browsing activity.
sudo pacman -S dnscrypt-proxy
NOTE: udp is required for dnscrypt protocol, keep this in mind when configuring your servers if your output chain is a default drop.
Edit /etc/dnscrypt-proxy/dnscrypt-proxy.toml to add your chosen resolvers etc.
For example, to setup ODoH you could do the following:
✔️ Click to Expand example dnscrypt-proxy.toml that is set up with dnsmasq as
the local resolver.
dnscrypt-proxy.toml that is set up with dnsmasq as
the local resolver.❗️ This isn’t the whole file, only the parts that were changed.
# DNSCRYPT servers to be forwarded to anon-relays
# server_names = ['quad9-dnscrypt-ip6-nofilter-pri', 'quad9-dnscrypt-ip4-nofilter-pri', 'quad9-dnscrypt-ip6-nofilter-ecs-pri' ]
# ODoH servers to be forwarded to odoh-relays
server_names = [ 'odoh-cloudflare', 'odoh-snowstorm' ]
listen_addresses = ['127.0.0.1:53', '[::1]:53']
max_clients = 250
# Use servers reachable over IPv4
ipv4_servers = true
# Use servers reachable over IPv6 -- Do not enable if you don't have IPv6 connectivity
ipv6_servers = true
# Use servers implementing the DNSCrypt protocol
dnscrypt_servers = true
# Use servers implementing the DNS-over-HTTPS protocol
doh_servers = false
# Use servers implementing the Oblivious DoH protocol
odoh_servers = true
## Require servers defined by remote sources to satisfy specific properties
# Server must support DNS security extensions (DNSSEC)
require_dnssec = true
# Server must not log user queries (declarative)
require_nolog = true
# Server must not enforce its own blocklist (for parental control, ads blocking...)
require_nofilter = true
bootstrap_resolvers = ['9.9.9.11:53', '1.1.1.1:53']
fallback_resolvers = ['1.0.0.1:53', '9.9.9.9:53']
ignore_system_dns = true
[sources]
### An example of a remote source from https://github.com/DNSCrypt/dnscrypt-resolvers
# These are just lists of resolvers to choose from
[sources.public-resolvers]
urls = [
'https://raw.githubusercontent.com/DNSCrypt/dnscrypt-resolvers/master/v3/public-resolvers.md',
'https://download.dnscrypt.info/resolvers-list/v3/public-resolvers.md',
]
cache_file = '/var/cache/dnscrypt-proxy/public-resolvers.md'
minisign_key = 'RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3'
refresh_delay = 73
prefix = ''
### Anonymized DNS relays
[sources.relays]
urls = [
'https://raw.githubusercontent.com/DNSCrypt/dnscrypt-resolvers/master/v3/relays.md',
'https://download.dnscrypt.info/resolvers-list/v3/relays.md',
]
cache_file = '/var/cache/dnscrypt-proxy/relays.md'
minisign_key = 'RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3'
refresh_delay = 73
prefix = ''
### ODoH (Oblivious DoH) servers and relays
[sources.odoh-servers]
urls = ['https://raw.githubusercontent.com/DNSCrypt/dnscrypt-resolvers/master/v3/odoh-servers.md', 'https://download.dnscrypt.info/resolvers-list/v3/odoh-servers.md']
cache_file = '/var/cache/dnscrypt-proxy/odoh-servers.md'
minisign_key = 'RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3'
refresh_delay = 73
prefix = ''
[sources.odoh-relays]
urls = ['https://raw.githubusercontent.com/DNSCrypt/dnscrypt-resolvers/master/v3/odoh-relays.md', 'https://download.dnscrypt.info/resolvers-list/v3/odoh-relays.md']
cache_file = '/var/cache/dnscrypt-proxy/odoh-relays.md'
minisign_key = 'RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3'
refresh_delay = 73
prefix = ''
### Quad9
[sources.quad9-resolvers]
urls = ['https://quad9.net/dnscrypt/quad9-resolvers.md', 'https://raw.githubusercontent.com/Quad9DNS/dnscrypt-settings/main/dnscrypt/quad9-resolvers.md']
minisign_key = 'RWTp2E4t64BrL651lEiDLNon+DqzPG4jhZ97pfdNkcq1VDdocLKvl5FW'
cache_file = '/var/cache/dnscrypt-proxy/quad9-resolvers.md'
prefix = 'quad9-'
[anonymized_dns]
# ODoH Server/ODoH Relay settings
routes = [
{ server_name='odoh-snowstorm', via=['odohrelay-crypto-sx'] },
{ server_name='odoh-cloudflare', via=['odohrelay-crypto-sx'] }
]
# DNSCRYPT Server/Anon-Relay Settings
# routes = [
# { server_name='quad9-dnscrypt-ip6-nofilter-pri', via=['anon-cs-berlin', 'anon-cs-dus6'] },
# { server_name='quad9-dnscrypt-ip4-nofilter-pri', via=['sdns://gREzNy4xMjAuMjE3Ljc1OjQ0Mw'] },
# { server_name='quad9-dnscrypt-ip6-nofilter-ecs-pri', via=['anon-cs-de', 'anon-cs-norway6'] }
# ]
## Skip resolvers incompatible with anonymization instead of using them directly
skip_incompatible = true
The only
ODoH relay
listed is odohrelay-crypto-sx so both ODoH servers are routed through the same
relay.
The commented out server_names and routes are for Anonymous relays, which
are different from oblivious relays and servers. Check the
relays.md
for different anon relays.
Resources:
✔️ Click to Expand Resources
Modify /etc/resolv.conf:
nameserver ::1
nameserver 127.0.0.1
options edns0
Stop it from being overwritten:
sudo chattr +i /etc/resolv.conf
Disable any services bound to port 53
ss -lp 'sport = :domain'
sudo systemctl stop systemd-resolved
sudo systemctl disable systemd-resolved
Enable dnscrypt-proxy:
sudo systemctl enable dnscrypt-proxy
libvirtd is another service that utilizes dnsmasq which uses port 53 and
conflicts with this setup. Since dnscrypt-proxy uses port 53 by default, it
makes using it with libvirtd difficult.
dnsmasq as a local DNS server
Edit the listen_addresses in /etc/dnscrypt-proxy/dnscrypt-proxy.toml:
listen_addresses = ['127.0.0.1:5353', '[::1]:5353']
Edit /etc/dnsmasq.conf:
listen-address=127.0.0.1,::1
server=127.0.0.1#5353
server=::1#5353
sudo systemctl enable dnsmasq
sudo systemctl restart dnsmasq
sudo systemctl restart dnscrypt-proxy
How it Works
We’ve created a two-step process for your DNS queries. Instead of your computer directly asking a DNS server for a website’s IP address, it now sends the request to dnsmasq first.
-
We configured dnsmasq to listen on 127.0.0.1 (our local machine). This means that all DNS queries from our system are sent to dnsmasq.
-
We told dnsmasq to use dnscrypt-proxy as its upstream DNS server with
server=127.0.0.1#5353. So, dnsmasq gets the DNS request and immediately forwards it to dnscrypt-proxy, which is listening on port 5353. -
dnscrypt-proxy then takes the forwarded request, encrypts it, and sends it to a secure DNS resolver on the internet. It receives the IP address back, decrypts it, and sends it back to dnsmasq.
-
Finally, dnsmasq receives the IP address from dnscrypt-proxy and sends it back to the program that made the original request (e.g., the browser).
Add Blocklists
Configure filter list sources in
/usr/share/dnscrypt-proxy/utils/generate-domains-blocklist/domains-blocklist.conf:
❗️ NOTE: Do a bit of research, all of these aren’t required and will slow down your queries. You can also setup an allowlist for exceptions.
# NextDNS CNAME cloaking list
https://raw.githubusercontent.com/nextdns/cname-cloaking-blocklist/master/domains
# AdGuard Simplified Domain Names filter
https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt
# OISD Big list
https://big.oisd.nl/domainswild
# HaGeZi Multi PRO
https://raw.githubusercontent.com/hagezi/dns-blocklists/main/wildcard/pro-onlydomains.txt
# HaGeZi Threat Intelligence Feeds
https://raw.githubusercontent.com/hagezi/dns-blocklists/main/wildcard/tif-onlydomains.txt
sudo mkdir -p /etc/dnscrypt-proxy-blocklist
sudo cp -r /usr/share/dnscrypt-proxy/utils/generate-domains-blocklist/* /etc/dnscrypt-proxy-blocklist/
Save
generate-domains-blocklist.py
to /usr/share/dnscrypt-proxy/utils/generate-domains-blocklist
cd /etc/dnscrypt-proxy-blocklist
sudo python3 generate-domains-blocklist.py -o blocklist.txt
Create a service to download & combine filter lists:
/etc/systemd/system/dnscrypt-filterlist-update.service:
[Unit]
Description=DNSCrypt Filterlist Update
[Service]
Type=oneshot
ExecStart=/etc/dnscrypt-proxy-blocklist/generate-domains-blocklist -a /etc/dnscrypt-proxy-blocklist/domains-allowlist.txt -o /etc/dnscrypt-proxy-blocklist/blocklist.txt
ExecStartPost=/usr/bin/sleep 2
ExecStartPost=/usr/bin/systemctl restart dnscrypt-proxy.service
Create a timer to run at boot & every 5 hours.
/etc/systemd/system/dnscrypt-filterlist-update.timer:
[Unit]
Description=Run filterlist update 15min after boot and every 5h
[Timer]
OnBootSec=15min
OnUnitActiveSec=5h
[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable dnscrypt-filterlist-update.timer
Configure dnscrypt-proxy to use the blocklist:
/etc/dnscrypt-proxy/dnscrypt-proxy.toml:
blocked_names_file = "/usr/share/dnscrypt-proxy/utils/generate-domains-blocklist/blocklist.txt"
log_file = '/var/log/dnscrypt-proxy/blocked-names.log'
For an allow list, you create a list of names you want to allow to bypass your blocklist:
allowed_names_file = "/usr/share/dnscrypt-proxy/utils/generate-domains-blocklist/allowed-names.txt"
log_file = "/var/log/dnscrypt-proxy/allowed-names.log"
Restart the service:
sudo systemctl restart dnscrypt-proxy
Reboot.
Verify blocklist works against an address listed in blocklist.txt:
dig dm.csl.academy @127.0.0.1 -p 53
Output:
;; ANSWER SECTION:
dm.csl.academy. 10 IN HINFO "This query has been locally blocked" "by dnscrypt-proxy"
Testing
I tested in Firefox by ensuring that in Settings -> Privacy & Security the DNS
over HTTPS was set to Status: Off, Enable DNS over HTTPS using: Off Use your
default DNS resolver.
Then go to https://dnsleaktest.com and perform an Extended Test. If you used
the above configuration with Quad9’s resolvers, you should see all WoodyNet ISPs
listed in Germany. If not, something is wrong and you have a DNS leak.
I have noticed dnsleaktest having issues lately, https://ipleak.net works for
this as well.
Enable MAC Randomization
sudo mkdir -p /etc/NetworkManager/conf.d
sudo hx /etc/NetworkManager/conf.d/20-mac-randomization.conf
Add the following for randomization (use stable for consistent per-network
MACs; change to random if preferred)
[device]
wifi.scan-rand-mac-address=yes
[connection]
wifi.cloned-mac-address=random
ethernet.cloned-mac-address=random
-
wifi.scan-rand-mac-address=yes: Enables randomization during Wi-Fi scans (default, but explicit for clarity). -
wifi.cloned-mac-address=stable/ethernet.cloned-mac-address=stable: Applies to Wi-Fi and Ethernet connections.
Verify MAC Randomization
Check your interface (e.g., wlp3s0 for Wi-Fi, find with ip link)
ip link show wlp3s0 | grep link/ether
nmcli connection down "YourSSID"
nmcli connection up "YourSSID"
Check MAC again:
cat /sys/class/net/wlp3s0/address
sudo systemctl status dnscrypt-proxy dnsmasq
nslookup example.com
Enable dnsmasq caching
/etc/NetworkManager/dnsmasq.d/cache.conf:
cache-size=1000
This may help with IPv6 connectivity, add the following to
/etc/NetworkManager/dnsmasq.d/ipv6-listen.conf:
listen-address=::1
Enable DNSSEC validation:
/etc/NetworkManager/dnsmasq.d/dnssec.conf:
conf-file=/usr/share/dnsmasq/trust-anchors.conf
dnssec
Enable Privacy Extensions
Add this to /etc/sysctl.d/40-ipv6.conf:
# Enable IPv6 Privacy Extensions
net.ipv6.conf.all.use_tempaddr = 2
net.ipv6.conf.default.use_tempaddr = 2
net.ipv6.conf.nic.use_tempaddr = 2
# find nic
ip link
net.ipv6.conf.wlp3s0.use_tempaddr = 2
sudo sysctl --system
/etc/NetworkManager/conf.d/ip6-privacy.conf:
[connection]
ipv6.ip6-privacy=2
Hardening Firefox on Arch
✔️ Click to Expand Table of Contents
🔑 Key Terms
-
Browser hardening focuses on reducing attack surface and blocking tracking by disabling or restricting features like JavaScript, cookies, telemetry, and third-party scripts.
-
Fingerprint protection, on the other hand, aims to make your browser indistinguishable from others. Instead of just blocking data collection, it ensures that your browser’s configuration; screen size, fonts, user agent, etc. matches a large group of users, so you blend in.
-
Browser compartmentalization is a technique where different browsers are dedicated to distinct online activities to isolate cookies, trackers, and browsing data. For example, Mullvad Browser can be used solely for activities where fingerprinting resistance is critical, such as anonymous browsing or visiting privacy-sensitive sites. Meanwhile, a hardened LibreWolf or Firefox can be used for general browsing, email, or banking where you want solid security and feature flexibility but aren’t as concerned about fingerprint uniqueness.
-
Web APIs: are sets of rules and protocols that allow browsers or servers to communicate and share data or functions over the internet. It lets developers access features or data of a web service or application without exposing the underlying system details, enabling different software to interact smoothly and securely.
-
Anonymity: Maximizing anonymity often means restricting or masking features (setting a generic fingerprint, disabling browser APIs, blocking trackers) so the browser blends in with many others. This reduces uniqueness but can break website functionality, cause CAPTCHAs, and limit usability.
-
Usability: Keeping your browser features enabled improves compatibility and user experience but increase uniqueness and thus make you easier to track.
-
Entropy: in this context, is a measure of how much unique information a specific browser feature contributes to your fingerprint. It’s often quantified in bits of entropy, where higher bits mean more uniqueness (i.e., easier to identify you).
- A “bit” is a basic unit of information for computers. Entropy measuring sites results are measured in “bits of identifying information”.
-
Origin: Web content’s origin is defined by the scheme (protocol), hostname (domain), and port of the URL used to access it. Two objects have the same origin only when the scheme, hostname, and port all match.
-
Same-origin policy: is a critical security mechanism that restricts how a document or script loaded by one origin can interact with a resource from another origin. It helps isolate potentially malicious documents, reducing possible attack vectors.
-
Firefox Site-Isolation. Firefox does provide site-isolation as well.
Tor Browser is not the most secure browser, anonymity, and security can often be at odds with each other. Having the exact same browser as many other people isn’t the best security practice, but it is great for anonymity. Tor is also based on Firefox Esr, which only receives patches for vulnerabilities considered Critical or High which can be taken advantage of.
Fingerprinting Explained
Modern Web APIs enable highly customized user experiences but also expose detailed device information that attackers can exploit to create browser fingerprints, unique identifiers used for covert tracking, even when cookies are blocked.
Entropy, a measure of randomness or uniqueness in data, is a critical metric for assessing the risk of browser fingerprinting.
Browser fingerprinting is a tracking technique, often done by third-party companies that specialize in it. They provide code (usually JavaScript) that a website owner can embed on their site. When you visit the site, the script runs in the background, silently collecting data about your device and browser.
There are two main approaches to obfuscating your fingerprint:
-
Standardization: Make browsers standardized and therefore have the same fingerprint to blend into a crowd. This is what Tor and Mullvad Browser do. Best for anonymity; increases the crowd you blend into, but may decrease usability (site breakage, CAPTCHAs); adversaries may still find subtle differences.
-
Randomization: Randomize fingerprint metrics so it’s not directly linkable to you. Brave has this feature, if you run coveryourtracks with Brave you will get a result of “your browser has a randomized fingerprint”. This is good for privacy but may be detectable by advanced scripts.
Test your browsers fingerprint:
Test how well your browser implements security standards and features:
Test the sites you visit for trackers:
Don’t put too much weight into the results as people often check their fingerprint, change one metric and check it again over and over skewing the results. It is helpful for knowing the fingerprint values that trackers track.
You can use something like NoScript to block JavaScript, preventing the scripts from running that do most of the fingerprinting. Extensions can make you more unique but it’s a give and take.
The following website lists the tracking protection mechanisms implemented by the major browsers and browser engines:
Metasearch Engines
SearXNG
SearXNG an open-source, privacy-respecting metasearch engine that aggregates
results from various search services, such as Google, DuckDuckGo, etc without
tracking you or profiling your searches. You can add SearXNG to firefox by going
to about:preferences#search and at the bottom click Add, URL will be
https://searx.be/search?q=%s.
❗️ NOTE: The above searx is the default and doesn’t give many relevant results. To get relevant results find a public instance with a good rating from your area and add the
search?q=%sto the end of it. For example, I’m usinghttps://priv.au/search?q=%s. This gives much better results than DDG in my opinion.
SearXNG is a bit different, you can choose which search engine you want for your
current search with !ddg search term to use duckduckgo for example.
Startpage is another metasearch engine that I’ve heard good things about.
Defenses
Encrypted DNS
DNS (Domain Name System) resolution is the process of translating a website’s domain name into its corresponding IP address. By default, this traffic isn’t encrypted, which means anyone on the network, from your ISP to potential hackers, can see the websites you’re trying to visit. Encrypted DNS uses protocols to scramble this information, protecting your queries and responses from being intercepted and viewed by others.
❗ NOTE: There are many other ways for someone monitoring your traffic to see what domain you looked up via DNS that it’s effectiveness is questionable without also using Tor or a VPN. Encrypted DNS will not help you hide any of your browsing activity.
There are 3 main types of DNS protection:
-
DNS over HTTPS (DoH): Uses the HTTPS protocol to encrypt data between the client and the resolver.
-
DNS over TLS (DoT): Similar to (DoH), differs in the methods used for encryption and delivery using a separate port from HTTPS.
-
DNSCrypt: Uses end-to-end encryption with the added benefit of being able to prevent DNS spoofing attacks.
Useful resources:
✔️ Click to Expand DNS Resources
Hot Take:
I recommend either setting up dnscrypt-proxy:
Or set Firefox’s DNS over HTTPS to Max protection with a custom resolver:
In about:preferences#privacy scroll down to DNS over HTTPS, Select
Max Protection -> Custom -> https://dns.quad9.net/dns-query
Enhanced Tracking Protection (ETP)
Browsers that have a form of tracking protection typically use lists of known trackers and match each outgoing request against these lists.
Enhanced Tracking Protection is how you deal with cookies and more on Firefox.
-
Enhanced Tracking Protection (ETP) blocks known “third-party requests” to companies that participate in fingerprinting, according to the Disconnect List
-
In
about:preferences#privacy, setting Enhanced Tracking Protection to either Strict or Custom enables FPP as well, explained further down. -
Total Cookie Protection is enabled by default in Standard mode.
-
When you set ETP to Strict, it includes Enhanced Cookie Clearing, which improves on the removal of third-party cookies, as well as Bounce Tracking Protection that prevents redirect trackers.
-
-
First-Party Isolation From the Tor Uplift Project.
Resist Fingerprinting
-
RFP (Resist Fingerprinting) set in
about:configwithprivacy.resistFingerprinting. Resist Fingerprinting alters the following:-
The timezone is reported as UTC or Icelandic
-
Locale is reported as en-US
-
Several properties of the navigator object are fixed, including the hardware concurrency value, application version and build ID. The User Agent version is reported to be the major version (for example, 119.0 instead of 119.1)
- And much more…
-
You can set
privacy.resistFingerprinting.pbMode(private-browsing) withoutprivacy.resistFingerprintingand still get certain Resist Fingerprinting behaviors on normal windows, because it’s impossible to separate these behaviors per-window.
-
-
FPP (Fingerprinting Protection) is enabled in normal browsing when Enhanced Tracking Protection is set to Strict. Both Known Fingerprinters and Suspected Fingerprinters Protection are enabled in Private Browsing and when ETP is set to strict. On the Custom level of ETP you can toggle on/off different features.
Disable JavaScript
Most trackers run on JavaScript, thus blocking JavaScript prevents them from gathering much of the info needed to form a browser fingerprint.
-
NoScript lets you selectively block scripts on websites. Its core function is to block all scripts by default on websites, allowing you to manually enable scripts on trusted sites.
NoScript PrivacyGuides should
-
uBlock Origin also lets you block JavaScript, just be aware that many sites may break so you’ll have to whitelist selectively.
Disable WebRTC
The main reason people disable WebRTC is to prevent an IP address leak. WebRTC is designed for real-time communication like video calls and file sharing. If you don’t use those features, it makes sense to disable it.
Disabling WebRTC eliminates specific data points that trackers use, reducing entropy & the uniqueness of your fingerprint.
In about:preferences#privacy -> Privacy & Security -> Permissions. Click
Settings.
-
Block new requests asking to access your microphone
-
Block new requests asking to access your camera
-
You may also want to block Location, but that isn’t involving WebRTC.
You can also use the master switch by going to about:config and setting
media.peerconnection.enabled to false
user.js settings related to WebRTC:
/* 2002: force WebRTC inside the proxy [FF70+] ***/
user_pref("media.peerconnection.ice.proxy_only_if_behind_proxy", true);
* When using a system-wide proxy, it uses the proxy interface
* [1] https://developer.mozilla.org/docs/Web/API/RTCIceCandidate
* [2] https://wiki.mozilla.org/Media/WebRTC/Privacy ***/
user_pref("media.peerconnection.ice.default_address_only", true);
user_pref("media.peerconnection.enabled", false);
user_pref("media.peerconnection.ice.obfuscate_host_address", true);
/* 2004: force exclusion of private IPs from ICE candidates [FF51+]
* [SETUP-HARDEN] This will protect your private IP even in TRUSTED scenarios after you
* grant device access, but often results in breakage on video-conferencing platforms ***/
user_pref("media.peerconnection.ice.no_host", true);
Disable Canvas Fingerprinting
Canvas Fingerprinting uses the HTML5 <canvas> element to generate a
fingerprint. It gets your device to render an image or text on a canvas and then
reads the pixel data. Since devices render things differently, those differences
are identifiable.
When the pref privacy.resistFingerprinting is set to true, restricts APIs
commonly used for fingerprinting, including the HTML5 Canvas API. When enabled,
it returns a randomized or generic canvas output to prevent unique
identification.
Disable WebGL
Similar to canvas fingerprinting, this technique uses the WebGL API to render 2D and 3D graphics. The way your system renders these graphics provides information about your GPU and graphics drivers, which contributes to your unique fingerprint.
❗️ NOTE: It’s unnecessary to disable webgl if you’re already using RFP and will likely make you stand out more because most people don’t disable it.
It can be disabled in about:config by setting webgl.disabled to true.
Install Firefox/LibreWolf & ArkenFox
⚠️ Firefox offers excellent privacy and customization but falls behind Chromium-based browsers in isolation and patch timing. Compartmentalization can help balance strong privacy with optimal security by using Brave or Chromium for high-risk browsing alongside Firefox for general and privacy-focused tasks.
Download Firefox from the Mozilla FTP site if you are worried about the download token, the FTP site lets you download the version you want without a token.
For example, for the latest firefox as of 09-30-25 for the US:
https://download.mozilla.org/?product=firefox-latest&os=linux64&lang=en-US
LibreWolf is an open-source fork of Firefox with a strong focus on privacy, security, and user freedom. LibreWolf enables always HTTPS, includes uBlockOrigin, and more providing strong defaults.
✔️ Click to Expand Arkenfox How To
The process is the same for both Firefox & LibreWolf. I like LibreWolf for it’s strong defaults but may lag behind Firefox getting security patches.
paru -S librewolf-bin
Read the ArkenFox Wiki
Apply
Open about:support and look for Profile Directory under Application Basics
Select Open Directory, it will bring you to somewhere like
~/.librewolf/pefoo8xx.default-default/ and that is where you place the
user.js.
Place the following files in your Profile Directory:
-
Arkenfox user.js Read through the
user.js, not all settings are applied by default such as RFP Fingerprinting protection. -
Also create your own
user-overrides.jswith any changes you want to make to the defaultuser.js. These changes are amended to theuser.jsand applied last enabling them to override the default settings. It’s best to make changes here so that updating the Arkenfoxuser.jsdoesn’t make you lose all of your customizations.
Example user-overrides.js spoofing the user agent:
❗️ This is just an example, always check for common useragent strings yourself. Read the Arkenfox Wiki!
<!-- user_pref( -->
<!-- "general.useragent.override", -->
<!-- "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", -->
<!-- ); -->
// Disable Activity Stream on new windows and tab pages
user_pref("browser.newtab.preload", false);
// Enhanced Tracking Protection (ETP)
user_pref("privacy.bounceTrackingProtection.mode", 1); // [FF131+] [ETP FF133+]
user_pref("privacy.trackingprotection.enabled", true);
// Resist Fingerprinting (RFP)
user_pref("privacy.resistFingerprinting", true); // [FF41+]
user_pref("privacy.resistFingerprinting.pbmode", true); // [FF114+]
// WebRTC
user_pref("media.peerconnection.enabled", false);
user_pref("media.peerconnection.ice.default_address_only", true);
// WebGL
user_pref("webgl.disabled", true);
// Geolocation
user_pref("geo.enabled", false);
user_pref("full-screen-api.enabled", false);
user_pref(
"geo.provider.network.url",
"https://location.services.mozilla.com/v1/geolocate?key=%MOZILLA_API_KEY%",
);
// Disable studies
user_pref("app.sheild.optoutstudies.enabled", false);
// Master Switches, Be Careful
user_pref("browser.safebrowsing.malware.enabled", false);
user_pref("browser.safebrowsing.phishing.enabled", false);
user_pref("browser.safebrowsing.provider.google4.gethashURL", "");
user_pref("browser.safebrowsing.provider.google4.updateURL", "");
user_pref("browser.safebrowsing.provider.google.gethashURL", "");
user_pref("browser.safebrowsing.provider.google.updateURL", "");
user_pref("browser.safebrowsing.provider.google4.dataSharingURL", "");
user_pref("signon.rememberSignons", false);
user_pref("browser.xul.error_pages.expert_bad_cert", true);
//* [NOTE] Will cause breakage: older modems/routers and some sites e.g banks, vimeo, icloud, instagram ***/
user_pref("network.http.referer.XOriginPolicy", 2);
user_pref("network.http.sendRefererHeader", 1);
// 0 is most strict:
user_pref("network.http.referer.trimmingPolicy", 0);
user_pref("network.http.referer.XOriginTrimmingPolicy", 2);
To apply your prefs you have to run the updater.sh script.
~/.mozilla/firefox/v5kwl3c0.default-release> ./updater.sh
############################################################################
#### ####
#### arkenfox user.js ####
#### Hardening the Privacy and Security Settings of Firefox ####
#### Maintained by @Thorin-Oakenpants and @earthlng ####
#### Updater for macOS and Linux by @overdodactyl ####
#### ####
############################################################################
Documentation for this script is available here: https://github.com/arkenfox/user.js/wiki/5.1-Updater-[Options]#-maclinux
Please observe the following information:
Firefox profile: /home/jr/.mozilla/firefox/v5kwl3c0.default-release
Available online: * version: 140
Currently using: * version: 140
This script will update to the latest user.js file and append any custom configurations from user-overrides.js. Continue Y/N?
y
Status: user.js has been backed up and replaced with the latest version!
Status: Override file appended: user-overrides.js
Check
Launch LibreWolf or Firefox and press Ctrl-Shift-J to launch Browser Console
Mode, and look for any errors.
Go to about:config -> [x] Show only modified preferences. You should see
SUCCESS: No no he's not dead, he's, he's restin'!
User Agent Spoofing
❗️ NOTE: Spoofing your useragent alone likely isn’t worth the protections you lose from disabling
resistFingerprinting.resistFingerprintingspoofs many different fingerprinting aspects. If you don’t get it right, you will get captcha requests constantly.
Research what the most common user agent is. You’ll need to disable
privacy.resistFingerprinting for this to work.
Place the user agent string in general.useragent.override something like:
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36
https://www.whatsmyuseragent.com
https://www.useragentstring.com
uBlock Origin
BetterFox does a great job explaining how to use uBlock with solid recommendations.
If you wanted to apply the
Privacy Essentials
list you would click
subscribe,
which launches the uBlock asset viewer where you can see all of the domains that
will be blocked before clicking Subscribe again to apply them.
Adding other lists
Click the uBlock logo, Settings, Filter lists, scroll to the bottom and choose Import…, Paste the url of your chosen list, and click Apply changes.
For example, Arkenfox suggests adding the Actually Legitimate URL Shortener Tool. Add https://raw.githubusercontent.com/DandelionSprout/adfilt/master/LegitimateURLShortener.txt to the Import… section and click Apply changes. If you scroll up, you’ll see that it was added and chosen.
Setup your Blocking mode
Many opt for medium mode. To do so you need to:
Settings pane:
- Open the dashboard, and choose I am an advanced user.
Filter lists pane:
-
All of uBO’s filter lists: checked
-
EasyList: checked
-
Peter Lowe’s Ad server list: checked
-
EasyPrivacy: checked
-
Online Malicious URL Blocklist: checked
My rules pane (4th pane from the left):
Underneath Temporary rules add, you literally type this in:
-
Add
* * 3p-script block -
Add
* * 3p-frame block -
Click
Save -
Click
<- Commit(Not required on phone)
Fallback to easy mode
Click the uBlock logo while on the site you want to use easy mode on. You’ll see the power button to the top right, the Global rules and the Local rules which start 3 boxes from the left. Click the box under the Local rules for 3rd-party scripts. The box will change colors, dark gray = NOOP (No Operation). This tells uBO to ignore the aggressive global block for this site, and let the normal filter lists handle the blocking.
You can also disable JavaScript in My rules as needed.
Set up a SOCKS5 Proxy with Arti
❗️ NOTE: This is one example of using a SOCKS5 proxy to circumvent censorship or add additional privacy without the Tor Browser. You can also route other apps through the proxy, such as email clients, messaging apps, torrent clients, and more.
- Clone the arti repo:
# clone the repo
git clone https://gitlab.torproject.org/tpo/core/arti.git
# navigate to the directory
cd arti
- To build the Arti binary, compile the code and generate the executable run:
These are the safer build options so you can leave the arti repo in your home directory without it leaking your username:
RUSTFLAGS="--remap-path-prefix $HOME/.cargo=.cargo --remap-path-prefix $(pwd)=." \
cargo build --release -p arti
- To allow Arti SOCKS proxy traffic you need to add a rule permitting incoming connections to port 9150.
For nftables, you would open /etc/nftables.conf and add:
chain input {
# ...snip...
# Allow Arti SOCKS proxy (port 9150)
tcp dport 9150 ct state new accept
# ...snip...
}
Enable it with sudo nft -f /etc/nftables.conf
- To run Arti as a SOCKS proxy on port
9150, execute:
./target/release/arti proxy
- Configure LibreWolf/Firefox to use the Arti proxy:
Open LibreWolf or Firefox
Go to the menu and open Preferences/Settings.
Scroll to the bottom Network Settings section.
Click on “Settings...” under Network Settings.
In the connection settings dialog:
Select “Manual proxy configuration”.
For “SOCKS Host”, enter 127.0.0.1.
For the port next to SOCKS Host, enter 9150.
Select the SOCKS version 5 option (SOCKS v5).
Optionally check the box “Proxy DNS when using SOCKS v5” to route DNS queries
through the proxy for enhanced privacy.
Click “OK” to apply the settings.
- Verify Your Proxy Setup Open a new tab and visit
https://dnsleaktest.comand run anExtended Test.
Your IP address should now appear as a Tor exit node IP, indicating your traffic is routed through the Arti proxy.
-
Make sure Arti is running in its terminal or background before you start browsing.
-
If you close the terminal or stop Arti, your browser will lose the proxy connection.
This setup only proxies the configured browser traffic; other apps are not affected unless configured similarly.
This setup turns LibreWolf or Firefox into a Tor-enabled browser without installing the Tor Browser Bundle, using the Arti SOCKS proxy instead. It can be useful if you want to use a more customizable or alternative browser while still accessing the Tor network securely.
⚠️ While using LibreWolf with the Arti SOCKS5 proxy provides network-level anonymity by routing traffic through the Tor network, it does not include the extensive browser-level privacy and security enhancements found in the official Tor Browser. For casual or moderate privacy needs the SOCKS proxy can be useful but for stronger anonymity guarantees and protection, the Tor Browser is recommended.
Setup an Arti service to run in the background
Be careful here, its not as easy to tell if Arti failed for some reason.
Create a service file at /etc/systemd/system/arti.service:
Replace your-username with your username
[Unit]
Description=Arti Tor Proxy Service
After=network.target
[Service]
ExecStart=/home/your-username/arti/target/release/arti proxy
Restart=on-failure
User=jr
Group=jr
WorkingDirectory=/home/your-username/arti
Environment=RUSTFLAGS="--remap-path-prefix $HOME/.cargo=.cargo --remap-path-prefix $(pwd)=."
[Install]
WantedBy=multi-user.target
Enable & Start the service:
sudo systemctl enable arti
sudo systemctl start arti --now
Ensure its running:
sudo systemctl status arti
✔️ Click to Expand Resources
-
ArkenFox user.js Just right IMO.
-
BetterFox user.js Easiest to use with less breakage.
-
Narsil user.js Most hardened.
If you should trust the U.S. Governments recommendations is another story but it can be good to compare and contrast with other trusted resources. You’ll have to think whether the CISA recommending that everyone uses Signal is solid advice or guiding you towards a honeypot, I can’t say for sure.
-
Mozilla Firefox Security Technical Implementation Guide The STIG for Mozilla Firefox (Security Technical Implementation Guide) is a set of security configuration standards developed by the U.S. Department of Defense. They are created by the Defense Information Systems Agency (DISA) to secure and harden DoD information systems and software.
-
Firefox Relay can be used to create email aliases that forward to your real email address. The paid plan also lets you create phone number aliases that forward to your phone number.
Cheatsheets
Hardening Arch Linux
✔️ Click to Expand Table of Contents
- The Basics
- Hardening/Sandboxing systemd
- Lynis and other tools
- Hardening the Kernel
- Hardening your current Kernel
- Disable Core Dumps
- Kernel and Namespace Hardening and Blacklisting
- Firewall
- SSH & GPG Key Generation and Safety
- Hardening OpenSSH
- USB Port Protection
- Hardening Firejail
- AppArmor
- Creating or adding profiles that aren’t pre-configured
- Intrusion Detection
⚠️ Warning: I am not a security expert. This guide presents various options for hardening Arch Linux, but it is your responsibility to evaluate whether each adjustment suits your specific needs and environment. Security hardening and process isolation can introduce stability challenges, compatibility issues, or unexpected behavior. Additionally, these protections often come with performance tradeoffs. Always conduct thorough research, there are no plug and play one size fits all security solutions.
Much of this guide draws inspiration or recommendations from the well-known Linux Hardening Guide by Madaidan’s Insecurities and the Arch Wiki. Arch is great but when anonymity truly matters, use Whonix or Tails. The Whonix Docs have a specific section or Arch with KVM that is an excellent resource.
- Whonix KVM#Arch_Linux, if you
plan on using this think twice before enabling
dnscrypt-proxyand you might want to consider ufw firewall rather than the nftables I share in this guide because of Port conflicts.
Keep in mind that Arch’s security posture is ultimately constrained by the broader limitations of Linux security mechanisms, upstream tooling, and ongoing development priorities.
I will assume that you’re using a UKI with Secure Boot enabled with systemd-boot for this section. If you don’t and want to, you can follow the Guide that I wrote or more obviously, the Wiki.
The Basics
Check the Arch Linux Security Tracker for information about known vulnerabilities affecting Arch packages. It lists CVEs (Common Vulnerabilities and Exposures) affecting packages, the severity, the fixed versions, and advisory details.
Backup your system regularly, many guides consider the data already lost if it isn’t backed up because without a backup, any data loss event (hardware failure, ransomware attack, accidental deletion, natural disaster) becomes irreversible. Backups act as a critical safety net that enables recovery. Without them, once data is corrupted, deleted, or encrypted, there is no reliable way to restore it, effectively making it lost permanently.
The 3-2-1 Backup Rule:
-
3 copies of data. (Ensuring redundancy)
-
2 storage types. (Reduces risk)
-
1 offsite backup. (Protects against cyber threats and natural disasters)
-
There is a backup guide for btrfs in enc_install
Use Full Disk Encryption to protect your data at rest.
Encryption is the process of using an algorithm to scramble plaintext data into ciphertext, making it unreadable except to a person who has the key to decrypt it.
Data at rest is data in storage, such as a computer’s or a servers hard disk.
Data at rest encryption (typically hard disk encryption), secures the documents, directories, and files behind an encryption key. Encrypting your data at rest prevents data leakage, physical theft, unauthorized access, and more as long as the key management scheme isn’t compromised.
Enable a UEFI password or Administrator password where it requires authentication in order to access the UEFI/BIOS.
Use a different password/passphrase for encryption and userspace.(i.e., user passwd)
Use Secure Boot, with a Unified Kernel Image (UKI). TPM can, in some ways increase security; I may add a section in the future.
When Secure Boot is used with a Unified Kernel Image, it provides enhanced protection compared to traditional boot methods because when Secure Boot verifies the UKI’s signature, it ensures the integrity and authenticity of:
-
The kernel itself,
-
The
initramfs, -
The kernel command line parameters,
-
And indirectly, the bootloader that loads the UKI (since the bootloader verifies the UKI signature before execution).
-
Secure boot doesn’t protect against runtime kernel exploits.
This means that any tampering with these components, such as injecting malware
into the initramfs, altering boot parameters, or swapping the kernel would
break the signature verification and prevent the system from booting.
systemd-boot has a smaller attack surface and is often recommended over GRUB
for hardened systems.
Useful Resources:
✔️ Click to Expand Secure Boot Resources
-
When using a UKI with Secure Boot, the initrd is authenticated because it’s a sealed component of the single signed image.
Best Practices and Standards
It’s crucial to document every system change meticulously. Since Arch Linux typically relies on manual configuration and does not use full declarative version control by default, maintaining clear records, such as detailed changelogs, notes, or Git repositories for your config files is essential.
A few options to version control some of your dotfiles:
Tools like GNU Stow and chezmoi help bring structure and version control to manual configurations in Arch Linux.
-
GNU Stow uses a symlink farm approach to manage dotfiles and configuration directories cleanly, making it easy to track and revert changes by organizing files under a single version-controlled directory.
-
chezmoi is a powerful dotfile manager focused on reproducible, encrypted, and template-driven config management. It simplifies applying changes across multiple machines and maintaining a documented history of modifications.
Using these tools enhances your ability to maintain clear, version-controlled records of system changes. Consider making your dotfiles repo private if you’re unsure what you need to protect.
By breaking changes into smaller, manageable tasks and documenting them with descriptive messages, you create a clear history of modifications. This makes it far simpler to troubleshoot issues, revert problematic changes, and maintain security best practices over time.
User and Permission Management
-
Implement distinct user accounts to minimize the risk associated with compromised accounts.
-
Execute Specific Commands: Always execute the specific command that requires elevation, rather than using sudo to open an entire root shell:
-
Good:
sudo pacman -Syu -
Bad: Running
sudo su -and then runningpacman -Syu
-
-
Consider using a more secure or minimal form of privilege escalation like
doas,sudo-rs, orrun0instead of standardsudo.
User and group management & Strong Passwords
-
Users and groups are used as a form of access control. They control access to the system’s files, directories, and peripherals. This is a security feature that limits access in certain specific ways.
- When granting access to a resource, create a specific group and add only the necessary users to it, rather than granting broad access to all root or wheel users. Demonstrated in: Doas over sudo.
-
Apply the principle of least privilege by assigning users only the permissions they actually need.
-
Avoid running services or applications as root; use dedicated service accounts where possible.
Passwords/Password Quality
sudo pacman -S libpwquality
Edit /etc/pam.d/passwd file to read as:
#%PAM-1.0
password required pam_pwquality.so retry=2 minlen=10 difok=6 dcredit=-1 ucredit=-1 ocredit=-1 lcredit=-1 [badwords=myservice mydomain] enforce_for_root
password required pam_unix.so use_authtok sha512 shadow
The /etc/pam.d/passwd file with pam_pwquality.so specifies the enforcement
directives and some immediate password rules, which apply dynamically during
password changes.
-
Use strong passwords and a password manager. The Arch Wiki Security section goes in depth about this.
-
Regularly review group memberships and file permissions, especially on sensitive system files.
Edit /etc/security/pwquality.conf to read as:
minlen = 15
# digit
dcredit = -1
# uppercase
ucredit = -1
# lowercase
lcredit = -1
# Special character
ocredit = -1
# dictionary checks
dictcheck = 1
# username checks
usercheck = 1
usersubstr = 5
enforcing = 1
# check roots pwquality
enforce_for_root
The /etc/security/pwquality.conf file holds system-wide default parameters
that cannot all be easy expressed on the PAM line. This is a bit more
comprehensive than /etc/pam.d/passwd.
Setting enforcing = 0 disables enforcement.
-
Using negative values like
dcredit = -1,ucredit = -1,lcredit = -1, andocredit = -1requires at least one digit, uppercase, lowercase, and other (special) character respectively. -
dictcheck = 1andusercheck = 1enable dictionary checks and user name checks to prevent weak or related passwords.
Package Management and System Minimalism
-
Favor packages from the official Arch repositories managed with pacman, as they are cryptographically signed and vetted by trusted Arch developers.
-
Use AUR packages cautiously; they lack official cryptographic signatures and require manual review and vetting before installation.
-
Install only the software and services you truly need to reduce the attack surface and minimize vulnerabilities.
-
Remove unused packages and disable unnecessary services to free up resources and limit potential entry points.
-
Consider using
arch-auditto find packages with vulnerabilities and patch said vulnerabilities.
Network and Privacy Best Practices
On a hardened Linux system, the browser is most often the weakest link exposed to the internet, and so security, privacy, and anti-tracking features of browsers are now as important, or even more important than platform-level protections.
-
Prefer software and hardware with privacy-respecting defaults and a strong security posture.
-
Use encrypted DNS and VPNs where possible: encrypted DNS protects your queries, while VPNs hide your IP address. Note that VPNs do not provide anonymity on their own and fingerprinting techniques can still reveal information.
-
Implement a firewall to control inbound and outbound network traffic based on predefined security rules. Firewalls serve as a critical layer of defense to reduce attack surfaces and monitor suspicious activity.
-
Secure SSH access by disabling root login, changing default ports, and enforcing public-key authentication to prevent brute-force and unauthorized access attempts.
Cryptography and Future-Proofing
-
The NSA, CISA, and NIST warn that nation-state actors are likely stockpiling encrypted data now, preparing for a future when quantum computers could break today’s most widely used encryption algorithms. Sensitive data with long-term secrecy needs is especially at risk.
-
This is a wake-up call to use the strongest encryption available today and to plan early for post-quantum security.
-
NIST First 3 Post-Quantum Encryption Standards Organizations and individuals should prepare to migrate cryptographic systems to these new standards as soon as practical.
-
They chose Four Quantum-Resistant Cryptographic Algorithms warning that public-key cryptography is especially vulnerable and widely used to protect digital information.
-
Follow developments from entities like NIST’s post-quantum encryption standards and implement recommendations as soon as practical.
-
Stay informed of quantum-resistant algorithm updates, especially for public-key cryptography, as recommended by NIST and security agencies.
Hardening/Sandboxing systemd
systemd is the core “init system” and service manager that controls how
services, daemons, and basic system processes are started, stopped and
supervised on modern Linux distributions, including Arch. It provides a suite of
basic building blocks for a Linux system as well as a system and service manager
that runs as PID 1 and starts the rest of the system.
Because it launches and supervises almost all system services, hardening systemd means raising the baseline security of your entire system.
sudo systemd-analyze security
# ...snip
systemd-hostnamed.service 1.7 OK 🙂
systemd-importd.service 5.0 MEDIUM 😐
systemd-journald.service 4.9 OK 🙂
systemd-logind.service 2.8 OK 🙂
systemd-machined.service 6.2 MEDIUM 😐
Check a specific unit:
sudo systemd-analyze security NetworkManager
sudo systemctl edit NetworkManager.service
→ Overall exposure level for NetworkManager.service: 9.6 UNSAFE 😨
The following file will open in your $EDITOR, these settings take it from UNSAFE to OK:
[Service]
NoNewPrivileges = true
ProtectHome = true
ProtectKernelModules = true
ProtectKernelLogs = true
ProtectControlGroups = true
ProtectClock = true
ProtectHostname = true
ProtectProc = "invisible"
ProtectKernelTunables=yes
PrivateTmp = true
RestrictRealtime = true
SystemCallFilter=~@mount ~@module ~@swap ~@obsolete ~@cpu-emulation ptrace
CapabilityBoundingSet=~CAP_KILL ~CAP_SYS_CHROOT ~CAP_AUDIT_* ~CAP_SETUID ~CAP_SETGID ~CAP_SETPCAP ~CAP_SYS_ADMIN ~CAP_NET_BIND_SERVICE ~CAP_NET_BROADCAST ~CAP_NET_RAW ~CAP_DAC_OVERRIDE ~CAP
_FOWNER ~CAP_IPC_OWNER
# Allow only the essential families (AF_PACKET and exotic ones are blocked by omission)
#RestrictAddressFamilies=AF_UNIX AF_NETLINK AF_INET AF_INET6
RestrictNamespaces = true
RestrictSUIDSGID = true
MemoryDenyWriteExecute = true
SystemCallArchitectures = "native"
LockPersonality= true
UMask=0077
User=root
PrivateDevices=yes
### Edits below this comment will be discarded
sudo systemctl daemon-reload
sudo systemctl restart NetworkManager
Sometimes you may need to reboot for the changes to take effect.
sudo systemd-analyze security NetworkManager
→ Overall exposure level for NetworkManager.service: 4.5 OK 🙂
❗️ This is just one example of the many services that you can harden if you so choose.
Further reading on systemd:
✔️ Click to Expand Systemd Resources
Lynis and other tools
Lynis is a security auditing tool for systems based on UNIX like Linux, macOS, BSD, and others.–lynis repo
sudo pacman -S lynis
List commands:
sudo lynis show commands
Commands:
lynis audit
lynis configure
lynis generate
lynis show
lynis update
lynis upload-only
Audit the system:
sudo lynis audit system
Lynis security scan details:
Hardening index : 83 [################ ]
Tests performed : 255
Plugins enabled : 0
-
The “Lynis hardening index” is an overall impression on how well a system is hardened. However, this is just an indicator on measures taken - not a percentage of how safe a system might be. A score over 75 typically indicates a system with more than average safety measures implemented.
-
Lynis will give you more recommendations for securing your system as well.
rkhunter (Rootkit Hunter):
sudo pacman -S rkhunter
Update the file properties database:
sudo rkhunter --propupd
Keep rkhunters data files up-to-date with:
sudo rkhunter --update
Run a system check:
sudo rkhunter --check --sk
Validate the config files:
sudo rkhunter --config-check
Get rid of false positives by adding the following to /etc/rkhunter.conf:
SCRIPTWHITELIST=/usr/bin/egrep
SCRIPTWHITELIST=/usr/bin/fgrep
SCRIPTWHITELIST=/usr/bin/ldd
SCRIPTWHITELIST=/usr/bin/vendor_perl/GET
Run a config check:
sudo rkhunter --config-check
ClamAV
-
Malware stands for malicious software. A computer virus is an undesirable program running on the user’s computer often without their consent or even without their knowledge.
sudo pacman -S clamav
Update the virus databases manually:
sudo freshclam
Scan your system:
sudo clamscan -r ~
----------- SCAN SUMMARY -----------
Known viruses: 8708646
Engine version: 1.4.3
Scanned directories: 6505
Scanned files: 79796
Infected files: 0
Data scanned: 4387.29 MB
Data read: 5191.41 MB (ratio 0.85:1)
Time: 1748.802 sec (29 m 8 s)
Start Date: 2025:10:03 14:32:58
End Date: 2025:10:03 15:02:07
❗️ NOTE: This can take a while, I recommend using
caffeine-ngorcaffeinefor this to prevent your system going to sleep while the scan completes.
paru -S caffeine-ng
This creates a coffee cup icon in your bar config on next reboot. If you’re in the middle of something and don’t want to reboot run:
caffeine &
Click the icon and Enable Caffeine to prevent sleep.
Hardening the Kernel
Given the kernel’s central role, it’s a frequent target for malicious actors, making robust hardening essential.
You can use the linux-hardened kernel to have a kernel that prioritizes
security over anything else:
sudo pacman -S linux-hardened linux-hardened-headers
Generate the initramfs for the hardened kernel:
sudo mkinitcpio -p linux-hardened
Configure systemd-boot to boot the hardened kernel, the entries are configured
under /boot/loader/entries/. Create or edit an entry file, for example
/boot/loader/entries/arch-linux-hardened.conf:
title Arch Linux Hardened
linux /vmlinuz-linux-hardened
initrd /initramfs-linux-hardened.img
options rd.luks.name=YOUR_UUID=cryptroot root=/dev/mapper/cryptroot rw quiet
Replace YOUR_UUID with the UUID of the encrypted root partition (from blkid)
To set this as the default boot entry and enable booting the last selected
kernel automatically, edit /boot/loader/loader.conf:
default arch-linux-hardened.conf
timeout 4
console-mode auto
Edit /etc/mkinitcpio.d/linux-hardened.preset:
# mkinitcpio preset file for the 'linux-hardened' package
ALL_config="/etc/mkinitcpio.conf"
ALL_kver="/boot/vmlinuz-linux-hardened"
PRESETS=('default' 'fallback')
#default_config="/etc/mkinitcpio.conf"
# default_image="/boot/initramfs-linux-hardened.img"
default_uki="/boot/EFI/Linux/arch-linux-hardened.efi"
default_options="--splash /usr/share/systemd/bootctl/splash-arch.bmp"
#fallback_config="/etc/mkinitcpio.conf"
# fallback_image="/boot/initramfs-linux-hardened-fallback.img"
fallback_uki="/boot/EFI/Linux/arch-linux-hardened-fallback.efi"
fallback_options="-S autodetect"
Reboot and linux-hardened should be chosen by default. You can also choose
additional kernels if needed.
Hardening your current Kernel
Sometimes linux-hardened just won’t work on your system without some serious
digging. You can harden your current kernel, or even better would be to harden
the Long-Term Support (LTS) kernel.
The Linux kernel is typically released under two forms: stable and long-term support (LTS). Choosing either has consequences, do your research. Stable vs. LTS kernels
See which kernel you’re currently using with:
# show the kernel release
uname -r
# show kernel version, hostname, and architecture
uname -a
Show the configuration of your current kernel:
zcat /proc/config.gz
sysctl is a tool that allows you to view or modify kernel settings and
enable/disable different features.
Check what each setting does sysctl-explorer
Refer to madadaidans-insecurities#sysctl-kernel for the following settings and their explainations.
Create a file /etc/sysctl.d/99-custom.conf, since files are read in
lexicographical order this file will be read last, allowing it to override any
settings from earlier files.
To check if a parameter is already set:
sysctl fs.protected_symlinks
sysctl -a | grep fs.protected
To list all parameters:
sysctl -a > params.txt
-
1typically means enable -
0typically means disable
Example, both of these were the default in the zen-kernel:
# 99-custom.conf
# prevent hardlink misuse
fs.protected_hardlinks = 1
# prevent symlink misuse
fs.protected_symlinks = 1
Apply the changes immediately:
sudo sysctl --system
Check Active Linux Security Modules:
cat /sys/kernel/security/lsm
# Output:
File: /sys/kernel/security/lsm
capability,landlock,yama,bpf,apparmor
Check Kernel Configuration Options:
zcat /proc/config.gz | grep CONFIG_SECURITY_SELINUX
zcat /proc/config.gz | grep CONFIG_HARDENED_USERCOPY
zcat /proc/config.gz | grep CONFIG_STACKPROTECTOR
sudo pacman -S kernel-hardening-checker
While in the same directory as the params.txt that we created earlier, run:
kernel-hardening-checker -l /proc/cmdline -c /proc/config.gz -s ./params.txt
Only the warnings listed with | sysctl | can be edited with the above method.
Example 99-custom.conf with some settings to prevent breakage:
✔️ Click to Expand `99-custom.conf` example
⚠️ WARNING: Always do your own research, you can find explanations to the following settings in: madaidans insecurities Linux Hardening guide.
You can apply these on top of the hardened kernel as well:
# Kernel Security Hardening
# ----------------------------------------------------------------------
# allow set-user-ID processes to dump core
fs.suid_dumpable = 2
# prevent pointer leaks
kernel.kptr_restrict = 2
# restrict kernel log to CAP_SYSLOG capability
kernel.dmesg_restrict = 1
# Note: certian container runtimes or browser sandboxes might rely on the following
# restrict eBPF to the CAP_BPF capability
kernel.unprivileged_bpf_disabled = 1
# should be enabled along with bpf above
# net.core.bpf_jit_harden = 2
# restrict loading TTY line disciplines to the CAP_SYS_MODULE
dev.tty.ldisk_autoload = 0
# prevent exploit of use-after-free flaws
vm.unprivileged_userfaultfd = 0
# kexec is used to boot another kernel during runtime and can be abused
kernel.kexec_load_disabled = 1
# Kernel self-protection
# SysRq exposes a lot of potentially dangerous debugging functionality to unprivileged users
# 4 makes it so a user can only use the secure attention key. A value of 0 would disable completely
kernel.sysrq = 4
# disable unprivileged user namespaces, Note: Docker and other apps may need this
# kernel.unprivileged_userns_clone = 0 # commented out because it makes apps I need fail
# restrict all usage of performance events to the CAP_PERFMON capability
kernel.perf_event_paranoid = 3
# Network Security Hardening
# ----------------------------------------------------------------------
# protect against SYN flood attacks (denial of service attack)
net.ipv4.tcp_syncookies = 1
# protection against TIME-WAIT assassination
net.ipv4.tcp_rfc1337 = 1
# enable source validation of packets received (prevents IP spoofing)
net.ipv4.conf.default.rp_filter = 1
net.ipv4.conf.all.rp_filter = 1
# Disable ICMP redirects for IPv4
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv4.conf.all.secure_redirects = 0
net.ipv4.conf.default.secure_redirects = 0
# Disable ICMP redirects for IPv6 (Protect against IP spoofing)
net.ipv6.conf.all.accept_redirects = 0
net.ipv6.conf.default.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0
# prevent man-in-the-middle attacks (by ignoring ICMP echo requests)
net.ipv4.icmp_echo_ignore_all = 1
# ignore bogus ICMP errors, helps avoid Smurf attacks
net.ipv4.icmp_ignore_bogus_error_responses = 1
# Disable IP forwarding
net.ipv4.conf.all.forwarding = 0
# Disable acceptance of source-routed packets for IPv4
net.ipv4.conf.default.accept_source_route = 0
net.ipv4.conf.all.accept_source_route = 0
# Disable acceptance of source-routed packets for IPv6
net.ipv6.conf.all.accept_source_route = 0
net.ipv6.conf.default.accept_source_route = 0
# Disable IPv6 forwarding
net.ipv6.conf.all.forwarding = 0
# Disable Router Advertisements acceptance
net.ipv6.conf.all.accept_ra = 0
net.ipv6.conf.default.accept_ra = 0
# TCP Hardening
# ----------------------------------------------------------------------
# Disable TCP SACK (Selective Acknowledgement)
net.ipv4.tcp_sack = 0
net.ipv4.tcp_dsack = 0
net.ipv4.tcp_fack = 0
# Userspace/Memory Security
# ----------------------------------------------------------------------
# restrict usage of ptrace
kernel.yama.ptrace_scope = 2
# ASLR memory protection (64-bit systems)
vm.mmap_rnd_bits = 32
vm.mmap_rnd_compat_bits = 16
# only permit symlinks to be followed when outside of a world-writable sticky directory
fs.protected_symlinks = 1
fs.protected_hardlinks = 1
# Prevent creating files in potentially attacker-controlled environments
fs.protected_fifos = 2
fs.protected_regular = 2
# Randomize memory
kernel.randomize_va_space = 2
# Exec Shield (Stack protection)
# NOTE: This is generally deprecated/obsolete on modern kernels that use other hardening measures
# kernel.exec-shield = 1
# TCP Optimization
# ----------------------------------------------------------------------
# TCP Fast Open: 3 = enable for both incoming and outgoing connections
net.ipv4.tcp_fastopen = 3
# Bufferbloat mitigations + slight improvement in throughput & latency
net.ipv4.tcp_congestion_control = bbr
net.core.default_qdisc = cake
Disable Core Dumps
A core dump consists of the recorded state of the working memory of a computer program at a specific time, generally when a program crashes or terminates abnormally. They can be useful for debugging but can also contain sensitive information, to disable core dumps:
Create /etc/sysctl.d/50-coredump.conf:
# disable coredumps
kernel.core_pattern = |/bin/false
# disable core dumps from setuid binaries
fs.suid_dumpable = 0
Apply with:
sudo sysctl --system
# or
sudo sysctl -p /etc/sysctl.d/50-coredump.conf
Arch uses systemd-coredump by default, which still intercepts dumps. To fully
disable that component, create /etc/systemd/coredump.conf/custom.conf:
[Coredump]
Storage=none
ProcessSizeMax=0
Then reload systemd:
sudo systemctl daemon-reload
sudo systemctl mask systemd-coredump.socket
Kernel and Namespace Hardening and Blacklisting
-
Protect the kernel image:
-
Enable Secure Boot to ensure only trusted boot components are loaded.
-
Enforce kernel module signature verification by adding
module.sig_enforce=1to your kernel parameters (e.g., create/etc/cmdline.d/security.confwith this line). This restricts loading unsigned kernel modules. -
NOTE: This setting is strict and may break compatibility with some drivers or modules. Thorough testing and research are recommended before enforcing it.
-
-
Protect the
/bootpartition:-
The above setting is fairly strict and will break some things. Do research before going so strict.
-
Make
/bootread-only if you have a high threat model. It can cause issues though, setting more strict permissions helps.
-
-
Lock kernel modules (optional):
- Set
module.sig_enforce=1in kernel parameters.(i.e.,/etc/cmdline.d/security.conf)
- Set
Blacklist unneeded modules in /etc/modprobe.d/blacklist.conf:
blacklist dccp # Datagram Congestion Control Protocol
blacklist sctp # Stream Control Transmission Protocol
blacklist rds # Reliable Datagram Sockets
blacklist tipc # Transparent Inter-Process Communication
blacklist n_hdlc # High-level Data Link Control
blacklist ax25 # Amateur X.25
blacklist netrom # NetRom
blacklist x25 # X.25
blacklist rose
blacklist decnet
blacklist econet
blacklist af_802154 # IEE 802.15.4
blacklist ipx # Internetwork Packet Exchange
blacklist appletalk
blacklist psnap # SubnetworkAccess Protocol
blacklist p8023 # Novell raw IEE 802.3
blacklist p8022 # IEE 802.3
blacklist can # Controller Area Network
blacklist atm
# Various rare filesystems
blacklist cramfs
blacklist freevxfs
blacklist jffs2
blacklist hfs
blacklist hfsplus
blacklist udf
# Less rare but often recommended
# Optionally blacklist squashfs, cifs, nfs etc. by uncommenting:
# blacklist squashfs
# blacklist cifs
# blacklist nfs
# blacklist nfsv3
# blacklist nfsv4
# blacklist ksmbd
# blacklist gfs2
# blacklist vivid
There are more suggestions in the madaidans insecurities guide.
Apply the changes:
sudo mkinitcpio -P
Check which modules were included in the initramfs (Long Output):
mkinitcpio -v > /tmp/mk.txt
Then search through the file and ensure they weren’t loaded.
❗️ Note: This may break some networking and virtualization tools.
-
Force-enable PTI (Page Table Isolation):
- Add
pti=onto the kernel command line.
- Add
-
User namespaces:
-
Do not set
kernel.unprivileged_userns_clone=0if desktop sandboxing/containers are used. -
Set
kernel.unprivileged_userns_clone=0in/etc/sysctl.d/to disable if containers are not required.
-
-
SMT/Hyperthreading policy:
-
For extra isolation, add
nosmtto kernel parameters. -
Disabling SMT reduces performance.
-
Firewall
nftables is designed to replace iptables by providing a modern,
simplified, and unified packet filtering and classification framework in the
Linux kernel. It reuses the underlying Netfilter infrastructure but introduces a
new kernel API and completely different user-space tool (nft).
Installation:
sudo pacman -S nftables
There is an example firewall ruleset located at /etc/nftables.conf and more
examples located in /usr/share/nftables/ and
/usr/share/doc/nftables/examples/
Flush existing iptables Rules & Disable iptables if necessary.
Create nftables ruleset:
#-----------------------------------------------------------------------------
# Flush existing rules: ensure a clean slate before loading new rules
#-----------------------------------------------------------------------------
flush ruleset
table inet filter {
# -------------------------------------------------------------------------
# INPUT CHAIN (Incoming Traffic destined for this host)
# Default is to DROP all incoming traffic
# -------------------------------------------------------------------------
chain input {
type filter hook input priority filter; policy drop;
# Drop invalid packets (e.g., malformed or out-of-state)
ct state invalid drop
# Allow loopback traffic (localhost to localhost)
iif "lo" accept
# Allow established and related connections (replies to outgoing, FTP helper)
ct state established,related accept
# Allow specific ICMP types for IPv4
ip protocol icmp icmp type { echo-request, echo-reply, destination-unreachable, time-exceeded } accept
# Allow critical ICMPv6 types for IPv6 (essential for network function)
ip6 nexthdr icmpv6 icmpv6 type { echo-request, echo-reply, destination-unreachable, packet-too-big, time-exceeded, parameter-problem, nd-router-advert, nd-router-solicit, nd-neighbor-solicit, nd-neighbor-advert } accept
# Allow SSH (port 2222) with rate-limiting to prevent brute-force attacks
tcp dport 2222 ct state new limit rate 15/minute accept
# Allow HTTP and HTTPS (ports 80 and 443)
tcp dport { 80, 443 } ct state new accept
# Log packets that reach the end of the chain before they are dropped by the policy (optional)
log prefix "nft-input-drop: "
}
# -------------------------------------------------------------------------
# FORWARD CHAIN (Routed Traffic passing through this host)
# Default is to DROP all routed traffic (host-based firewall)
# -------------------------------------------------------------------------
chain forward {
type filter hook forward priority filter; policy drop;
# Drop invalid packets
ct state invalid drop
# Allow established and related connections
ct state established,related accept
# Add specific FORWARD rules here if the machine is acting as a router/gateway
}
# -------------------------------------------------------------------------
# OUTPUT CHAIN (Outgoing Traffic originating from this host)
# Default is to DROP all outgoing traffic for maximum security
# -------------------------------------------------------------------------
chain output {
type filter hook output priority filter; policy drop;
# Allow essential local communication
oif "lo" accept
# Allow replies for established and related connections (critical)
ct state established,related accept
# Allow DNS queries (UDP and TCP)
udp dport 53 accept
tcp dport 53 accept
# Allow general outbound web access (e.g., for updates, API calls)
tcp dport { 80, 443 } accept
# Allow Git (and general SSH client) outgoing connections
tcp dport 22 ct state new accept
# Allow NTP (Network Time Protocol) for time synchronization
udp dport 123 accept
# Use meta l4proto to correctly match MLDv2 Type 143 packets
meta l4proto ipv6-icmp icmpv6 type {
echo-request,
destination-unreachable,
time-exceeded,
parameter-problem,
nd-neighbor-solicit,
nd-neighbor-advert,
mld2-listener-report
} accept
# Log packets that reach the end of the chain before they are dropped by the policy (optional)
log prefix "nft-output-drop: "
}
}
❗️ Most desktop firewalls default to allow all outgoing traffic (
policy accepton the output chain). This is done for convenience, as it prevents applications from breaking. However, for a system practicing zero trust, the best practice is to enforce a default deny(policy drop) on the output chain and only explicitly allow the services the system needs.
sudo systemctl enable nftables.service
sudo systemctl start nftables.service
Load and test the rules if in a different location than the default:
sudo nft -f /path/to/your/nftables.conf
or in default location:
sudo nft -f /etc/nftables.conf
❗️ If you don’t use SSH or host a web server or any service, don’t allow SSH and HTTP/HTTPS.
sudo nft list ruleset
SSH & GPG Key Generation and Safety
ssh-keygen
The ed25519 algorithm is significantly faster and more secure when compared to
RSA. You can also specify the key derivation function (KDF) rounds to
strengthen protection even more.
For example, to generate a strong key for MdBook:
ssh-keygen -t ed25519 -a 32
Or more specifically:
ssh-keygen -t ed25519 -a 32 -f ~/.ssh/id_ed25519_github_$(date +%Y-%m-%d) -C "SSH Key for GitHub"
-
-tis for type -
-a 32sets the number of KDF rounds. The standard is usually good enough, adding extra rounds can make it harder to brute-force. -
-fis for filename
GnuPG and gpg-agent
✔️ Click to Expand GnuPG section
gpg --full-generate-key can be used to generate a basic keypair.
gpg --expert --full-generate-key can be used for keys that require more
capabilities.
❗ NOTE: We will first generate our GPG primary key that is required to atleast have sign capabilities, we will then derive subkeys from said primary key and use them for signing and encrypting. It is recommended to generate a revoke certificate right after creating your primary key.
To generate your gpg primary key you can do the following:
gpg --full-generate-key
-
Choose
(10) (sign only) -
Give it a name and description
-
Give it an expiration date, 1y is common
-
Use a strong passphrase or password
-
Give it a comment, I typically add the date
If you see a warning about incorrect permissions, you can run the following:
chmod 700 ~/.gnupg
chmod 600 ~/.gnupg/*
Verify:
ls -ld ~/.gnupg
# Should show: drwx------
ls -l ~/.gnupg
# Files should show: -rw-------
Generate a Revocation Certificate
mykey must be a key specifier, either the keyID of the primary keypair or any
part of the user ID that identifies the keypair:
Replace mykeyID with the keyID of your primary key and store the cert in a
safe place:
gpg --output revoke.asc --gen-revoke mykeyID
Create a revocation certificate for this key? (y/N)
Please select the reason for the revocation:
0 = No reason specified
1 = Key has been compromised
2 = Key is superseded
3 = Key is no longer used
Q = Cancel
(Probably you want to select 1 here)
Your decision?
The certificate will be output to a file revoke.asc. If the --output is
ommitted, the result will be placed on stdout.
Since it’s a short certificate, you can print a hardcopy and store it somewhere safe. The cert shouldn’t be somewhere that others can access it since anyone could publish the revoke cert and render the corresponding public key useless.
To apply the revoke cert, import it:
gpg --import revoke.asc
# And optionally push the revoked key to public keyservers to notify others:
gpg --keyserver keyserver.ubuntu.com --send-keys YOUR_KEYID
Generate Gpg Subkeys
# Take note of your public key
gpg --list-keys --with-fingerprint
/home/jr/.gnupg/pubring.kbx
---------------------------
pub ed25519/0x095782A1B124AF15 2025-08-23 [SCA] [expires: 2026-08-23]
Key fingerprint = 5908 9C5B FEC8 0D75 FCB0 E206 0958 82C1 A124 CF15
uid [ultimate] Jr (08-23-25) <sayls8@proton.me>
- Copy the KeyID, in this example it would be
0x095722B2A123CF15. We will use it for the command below.
Now we will generate 2 subkeys, 1 for encryption and 1 for authentication.
gpg --expert --edit-key 0x095722B2A123CF15
Choose 11 (set your own capabilities) and add A (Authenticate) and type Q. Create another key while still in the menu with only encrypt capabilities.
❗
gpg --edit-keyhas many more capabilities, after launching typehelp.
Add Keygrip of Authenticate Subkey to sshcontrol for gpg-agent
gpg --list-secret-keys --with-keygrip --keyid-format LONG
Copy the keygrip of the subkey with Authenticate capabilities
echo "6BD11826F3845BC222127FE3D22C92C91BB3FB32" > ~/.gnupg/sshcontrol
ssh-add -L
# you should see something like:
ssh-ed25519 AABCC3NzaC1lZDI1NTE5ABBAIHyujgyCjjBTqIuFM3EMUSo6RGklmOXQW3uWRhWdJ1Mm (none)
- By itself, a keygrip cannot be used to reconstruct your private key. It’s derived from the public key material, not from the secret key itself so it’s safe to version control. Don’t put your keygrip in a public repo if you don’t want people to know you use that key for signing/authentication. It’s not a security risk, but it leaks a tiny bit of metadata.
The following article mentions the keygrip being computed from public elements of the key:
Create a ~/.gnupg/gpg.conf:
Copy the KeyID of the key with Authenticate capabilities and use it as your
default-key in gpg.conf:
#
# This is an implementation of the Riseup OpenPGP Best Practices
# https://help.riseup.net/en/security/message-security/openpgp/best-practices
#
#-----------------------------
# default key
#-----------------------------
# The default key to sign with. If this option is not used, the default key is
# the first key found in the secret keyring
#default-key 0xD8692123C4065DEA5E0F3AB5249B39D24F25E3B6
#-----------------------------
# behavior
#-----------------------------
# Disable inclusion of the version string in ASCII armored output
no-emit-version
# Disable comment string in clear text signatures and ASCII armored messages
no-comments
# Display long key IDs
keyid-format 0xlong
# List all keys (or the specified ones) along with their fingerprints
with-fingerprint
# Display the calculated validity of user IDs during key listings
list-options show-uid-validity
verify-options show-uid-validity
# Try to use the GnuPG-Agent. With this option, GnuPG first tries to connect to
# the agent before it asks for a passphrase.
use-agent
#-----------------------------
# keyserver
#-----------------------------
# This is the server that --recv-keys, --send-keys, and --search-keys will
# communicate with to receive keys from, send keys to, and search for keys on
keyserver hkps://keys.openpgp.org/
# Set the proxy to use for HTTP and HKP keyservers - default to the standard
# local Tor socks proxy
# It is encouraged to use Tor for improved anonymity. Preferrably use either a
# dedicated SOCKSPort for GnuPG and/or enable IsolateDestPort and
# IsolateDestAddr
#keyserver-options http-proxy=socks5-hostname://127.0.0.1:9050
# Don't leak DNS, see https://trac.torproject.org/projects/tor/ticket/2846
keyserver-options no-try-dns-srv
# When using --refresh-keys, if the key in question has a preferred keyserver
# URL, then disable use of that preferred keyserver to refresh the key from
keyserver-options no-honor-keyserver-url
# When searching for a key with --search-keys, include keys that are marked on
# the keyserver as revoked
keyserver-options include-revoked
#-----------------------------
# algorithm and ciphers
#-----------------------------
# list of personal digest preferences. When multiple digests are supported by
# all recipients, choose the strongest one
personal-cipher-preferences AES256 AES192 AES CAST5
# list of personal digest preferences. When multiple ciphers are supported by
# all recipients, choose the strongest one
personal-digest-preferences SHA512 SHA384 SHA256 SHA224
# message digest algorithm used when signing a key
cert-digest-algo SHA512
# This preference list is used for new keys and becomes the default for
# "setpref" in the edit menu
default-preference-list SHA512 SHA384 SHA256 SHA224 AES256 AES192 AES CAST5 ZLIB BZIP2 ZIP Uncompressed
Add the following to your shell config, either .bashrc or .zshrc:
GPG_TTY=$(tty)
export GPG_TTY
export SSH_AUTH_SOCK=$(gpgconf --list-dirs agent-ssh-socket)
gpgconf --launch gpg-agent
Rebuild and then restart gpg-agent if necessary:
gpgconf --kill gpg-agent
gpgconf --launch gpg-agent
Test, these should match:
echo "$SSH_AUTH_SOCK"
# output
/run/user/1000/gnupg/d.wft5hcsny4qqq3g31c76534j/S.gpg-agent.ssh
gpgconf --list-dirs agent-ssh-socket
# output
/run/user/1000/gnupg/d.wft5hcsny4qqq3g31c76834j/S.gpg-agent.ssh
ssh-add -L
# Copy the entire following line:
ssh-ed25519 AABBC3NzaC1lZDI1NTE5AAAAIGXwhVokJ6cKgodYT+0+0ZrU0sBqMPPRDPJqFxqRtM+I (none)
- It shows
(none)because the comment field is blank on subkeys.
Backing up Your Keys
gpg --export-secret-keys --armor --output my-private-key-backup.gpg
Your private keys will be encrypted with a passphrase into a .gpg file. Store this backup in a secure location line an encrypted USB drive. This can prevent you from losing access to your keys in the case of disk failure or accidents.
You can export your public keys and publish them publicly if you choose:
gpg --export --armor --output my-public-keys.gpg
Now if your keys ever get lost or corrupted, you can import these backups.
Remove and Store your Primary Key offline
❗ NOTE: After you remove your primary key, you will no longer be able to derive subkeys from it or sign keys unless you re-import it.
# extract the primary key
gpg -a --export-secret-key sayls8@proton.me > secret_key
# extract the subkeys, which we will reimport later
gpg -a --export-secret-subkeys sayls8@proton.me > secret_subkeys.gpg
# delete the secret keys from the keyring, so only subkeys are left
gpg --delete-secret-keys sayls8@proton.me
Delete this key from the keyring? (y/N) y
This is a secret key! - really delete? (y/N) y
# reimport the subkeys
gpg --import secret_subkeys.gpg
# verify everything is in order
gpg --list-secret-keys
# remove the subkeys from disk
rm secret_subkeys.gpg
I recommend also keeping a .gpg version to make it easy to re-import your
primary key: gpg --export-secret-keys --armor --output private-key-bak.gpg
Then store secret_key on an encrypted USB drive or somewhere offline. If you
want to protect it for now, you can just use the encryption subkey that we
created to encrypt secret_key with a passphrase:
gpg --list-keys --keyid-format LONG
Copy the KeyID of the subkey with encrypt capabilities for the following command:
# Encrypting your secret key for yourself
gpg --encrypt --recipient Ox37ACA569C5C44787 secret_key
You can check that the secret key material is missing with
gpg --list-secret-keys, you should see sec# instead of sec.
gpg --list-secret-keys
# Output:
sec# ed25519/0x
# ...snip...
The above set of commands are from the RiseUp Keep your primary key offline
Hardening OpenSSH
OpenSSH is a tool that allows you to remotely connect to your machine with the SSH protocol. It encrypts all traffic to prevent eavesdropping, connection hijacking, and other attacks.
Install and configure fail2ban:
sudo pacman -S fail2ban
Create a /etc/fail2ban/jail.local file:
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 5
bantime = 3600 # 1 hour in seconds
findtime = 600
ignoreip = 127.0.0.1/8 ::1
banaction = iptables-multiport
Start and enable the service:
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
Harden OpenSSH settings in /etc/ssh/sshd_config:
PasswordAuthentication no
PermitEmptyPasswords no
PubkeyAuthentication yes
AuthorizedKeysFile %h/.ssh/authorized_keys
UsePAM yes
PermitTunnel no
UseDNS no
KbdInteractiveAuthentication no
X11Forwarding no # or yes if you have X server enabled
MaxAuthTries 3
MaxSessions 2
ClientAliveInterval 300
ClientAliveCountMax 0
AllowUsers your-user
TCPKeepAlive no
AllowTcpForwarding no
AllowAgentForwarding no
LogLevel VERBOSE
PermitRootLogin no
KexAlgorithms curve25519-sha256@libssh.org,ecdh-sha2-nistp521,ecdh-sha2-nistp384,ecdh-sha2-nistp256,diffie-hellman-group-exchange-sha256
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-512,hmac-sha2-256,umac-128@openssh.com
Enable and start sshd:
sudo systemctl enable sshd
sudo systemctl start sshd
Ensure all of the permissions are correct:
chmod 755 $HOME
chmod 700 $HOME/.ssh
Add the output of ssh-add -L to ~/.ssh/authorized_keys
echo "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGXwhVokJ6cKgodYT+0+0ZrU0sBqMPPRDPJqFxqRtM+I (none)" > ~/.ssh/authorized_keys
After adding the authorized_key, adjust the permission:
chmod 600 $HOME/.ssh/authorized_keys
Finally, test an ssh connection:
ssh user@hostname
# Example
ssh jr@archlinux
# Specify the port
ssh -p 22 user@hostname
Tip Change the Default Port
⚠️ Critical Warning: Do not close your existing SSH session until you successfully connect using the new port. If you make a mistake and get locked out, you’ll need console access to your server to fix it
Edit /etc/ssh/sshd_config to add the line:
Port 2222
Update the Firewall Rules in /etc/nftables.conf:
Replace this line:
tcp dport ssh accept comment "allow sshd"
with:
tcp dport 2222 accept comment "allow sshd on port 2222"
This change explicitly allows new incomming TCP connections on port 2222 for SSH, ensuring remote access will work through the firewall.
Reload the Firewall:
sudo nft -f /etc/nftables.conf
Restart sshd:
sudo systemctl restart sshd
Finally, text a connection:
ssh -p 2222 user@hostname
USB Port Protection
It’s important to protect your USB ports to prevent BadUSB attacks, data exfiltration, unauthorized device access, malware injection, etc.
To get a list of your connected USB devices you can use lsusb from the
usbutils package.
lsusb
Install usbguard:
sudo pacman -S usbguard
# Optionally; paru -S usbguard-notifier fails with bad keys
# paru -S usbguard-notifier-git
Create a usbguard group and add your user to it:
sudo groupadd usbguard
sudo usermod -aG usbguard username
Generate a policy based on your currently attached USB devices with:
sudo usbguard generate-policy | sudo tee /etc/usbguard/rules.conf
# Or if everything else fails
# sudo bash -c "usbguard generate-policy > /etc/usbguard/rules.conf"
sudo chmod 600 /etc/usbguard/rules.d/99-policy.conf
USBGuard Daemon
❗️ If practicing zero-trust, you would want to change your default policy to
apply-policy. This way any device that isn’t explicitly allowed will be blocked. It’s easy to lock yourself out if done incorrectly.
Edit /etc/usbguard/usbguard-daemon.conf to set the policy to allow devices
that are already connected for members of the usbguard group:
# ...snip...
RuleFile=/etc/usbguard/rules.conf
# Default policy for devices that were already connected when the daemon started.
# Supported values: apply-policy, allow, block, reject, keep.
PresentDevicePolicy=allow
# A list of users and groups that are allowed to interact with the daemon
# via the IPC interface.
#IPCAllowedUsers=root your-user
IPCAllowedUsers=usbguard
From the above file we can see that it expects its configuration file to be
located at /etc/usbguard/rules.d/:
sudo mkdir -p /etc/usbguard/rules.d
Create a file /etc/usbguard/rules.d/99-policy.conf:
# allow `only` devices with mass storage interfaces (USB Mass Storage)
allow with-interface equals { 08:*:* }
# allow mice and keyboards
# allow with-interface equals { 03:*:* }
# Reject devices with suspicious combination of interfaces
reject with-interface all-of { 08:*:* 03:00:* }
reject with-interface all-of { 08:*:* 03:01:* }
reject with-interface all-of { 08:*:* e0:*:* }
reject with-interface all-of { 08:*:* 02:*:* }
The above policy can be found in RedHat UsbGuard
The only allow rule is for devices with only mass storage interfaces (08::)
i.e., USB Mass storage devices, devices like keyboards and mice (which use
interface class 03:*:*) implicitly not allowed.(commented out)
The reject rules reject devices with a suspicious combination of interfaces. A USB drive that implements a keyboard or a network interface is very suspicious, these reject rules prevent that.
The presentDevicePolicy = "allow" allows any device that is present at daemon
start up even if they’re not explicitly allowed. However, newly plugged in
devices must match an allow rule or get denied implicitly.
Enable/Start usbguard.service:
sudo systemctl enable usbguard
sudo systemctl start usbguard --now
- Sometimes a reboot is required. If your keyboard doesn’t work, after entering
your encryption passphrase, enter the TTY with
Alt+Ctrl+F2and ensure theusbguardgroup exists and your user is a member.
Check status:
sudo systemctl status usbguard
sudo usbguard list-devices
-
If you plug something new in and it doesn’t work, this is why. Remember this!
Firejail
❗️ Critics such as madaidan say that Firejail worsens security by acting as a privilege escalation hole. Firejail requires the executable to be setuid, meaning it runs with root privileges. Experienced users are encouraged to use bubblewrap for it’s minimal design and specificity of its purpose.
A setuid binary is an executable file with a special permission bit set called “set user ID” (setuid). When a user runs a setuid binary, the program executes with the permissions of the binary’s owner, rather than the permissions of the user running it.
There are mitigations for the above risks I will share further down.
Another option here is Bubblewrap
sudo pacman -S firejail
Usage:
firejail librewolf
Using by default:
sudo firecfg
fix .desktop files with:
firecfg --fix
This creates symbolic links in /usr/local/bin/ pointing to /usr/bin/firejail
for programs for which Firejail has default or self-created profiles.
You can inspect /etc/firejail/ to see all the pre-baked profiles available.
Hardening Firejail
Add the following line to /etc/firejail/firejail.config:
force-nonewprivs yes
- The above setting prevents Firejail and its child processes from gaining new
privileges after the sandbox is started.
- Changing the owner and group to
root:firejailand permissions to4750means Firejail runs with setuid root but only allows execution by users in the firejail group reducing the attack surface.
- Changing the owner and group to
❗️ This breaks some apps such as VirtualBox which I don’t recommend and if using the hardened kernel, Wireshark and Chromium-based browsers are also affected.
Add a pacman hook to automatically change firejail owner and mode. Create
/etc/pacman.d/hooks/firejail-permissions.hook and place the following in it:
[Trigger]
Operation = Install
Operation = Upgrade
Type = Package
Target = firejail
[Action]
Depends = coreutils
Depends = bash
When = PostTransaction
Exec = /usr/bin/sh -c "chown root:firejail /usr/bin/firejail && chmod 4750 /usr/bin/firejail"
Description = Setting /usr/bin/firejail owner to "root:firejail" and mode "4750"
Create a firejail group:
sudo groupadd firejail
and add your user to it:
sudo gpasswd -a $USER firejail
Verify Firejail’s being used:
Launch the program that you want to ensure is running sandboxed and run:
firejail --list
# or more comprehensive
firejail --tree
Enable AppArmor support:
sudo apparmor_parser -r /etc/apparmor.d/firejail-default
With firejail running, I noticed that none of my browsers would allow me to download anything. A fix for this is to run:
sudo firejail --noprofile firefox
Download your file, close firefox and run again in the firejail sandbox.
Tip from Arch Wiki
/etc/pacman.d/hooks/firejail.hookFor cases where you need to manually modify the
Exec=line of the .desktop file in~/.local/share/applicationsto explicitly call Firejail.[Trigger] Type = Path Operation = Install Operation = Upgrade Operation = Remove Target = usr/bin/* Target = usr/share/applications/*.desktop [Action] Description = Configure symlinks in /usr/local/bin based on firecfg.config... When = PostTransaction Depends = firejail Exec = /bin/sh -c 'firecfg >/dev/null 2>&1'
To manually map individual applications, execute:
sudo ln -s /usr/bin/firejail /usr/local/bin/application-to-sandbox
Remove Firejail symlinks
sudo firecfg --clean
If you would rather confine an app with AppArmor or Bubblewrap:
sudo rm /usr/local/bin/application
Also, comment out application in the /etc/firejail/firecfg.config to prevent
it from being added if you run firecfg again.
✔️ Click to Expand Firejail Resources
AppArmor
AppArmor is a Mandatory Access Control (MAC) system, implemented upon the Linux Security Modules(LSM) – Arch Wiki
MAC systems generally block all access by default, only permitting actions that are explicitly defined as allowed in their security policy or access profiles.
This is why, if you read about creating your own policy that they recommend setting AppArmor to complain mode while you use said app using all functionality and APIs you can think of before setting to enforce. Within a policy everything is default-deny so any action not covered in the above steps will be blocked by default.
❗️ The AppArmor policy con only be considered default deny if it is deployed as a complete system policy which we don’t do here. The apps and parts of the system that don’t have pre-defined policies aren’t covered by AppArmor so are therefore default allow. You can get there in time but it’s beyond the scope of this section.
Install:
sudo pacman -S apparmor
Edit /etc/cmdline.d/security.conf:
# enable apparmor
lsm=landlock,lockdown,yama,integrity,apparmor,bpf audit=1 audit_backlog_limit=256
Save & Reboot
Start/Enable AppArmor:
sudo systemctl enable apparmor
sudo systemctl start apparmor
Ensure the LSM is loaded with:
zgrep CONFIG_LSM=/proc/config.gz
# &
cat /sys/kernel/security/lsm
Reboot, and run sudo aa-enabled, and sudo aa-status. You should see many
profiles in enforce mode.
Switching an app from unconfined mode to enforce
- First, ensure a profile exists
Check AppArmor log messages:
Each time AppArmor denies applications from doing something potentially harmful the event is logged.
sudo journalctl -fx
NOTE: Your firewall can also trigger this.
Disabling AppArmor
Disable AppArmor by unloading all profiles for the current session:
sudo aa-teardown
Further reading:
Creating or adding profiles that aren’t pre-configured
For example, thunderbird came with a profile that just allowed everything.
Rather than creating my own profile I cloned the apparmor-profiles repo and
got it from there.
mkdir aa && cd aa
git clone git://git.launchpad.net/apparmor-profiles
sudo cp ~/aa/apparmor-profiles/thunderbird /etc/apparmor.d/usr.bin.thunderbird
Create a local override file, this is where you will make your personal changes to existing profiles:
sudo mkdir -p /etc/apparmor.d/local
sudo touch /etc/apparmor.d/local/usr.bin.thunderbird
Set the new profile to enforce mode:
sudo aa-enforce /etc/apparmor.d/usr.bin.thunderbird
Creating a new profile
- Temporarily disable kernel rate limiting on logs:
sudo sysctl -w kernel.printk_ratelimit=0
- Start
aa-genprof:
sudo aa-genprof <path-to-executable>
# Example
sudo aa-genprof /usr/bin/brave
- Execute a test plan, open and close brave, download a file through brave, adjust settings, do anything you can think of with brave.
-
Press S to Scan system log for AppArmor events
-
Press F to Finish
This simple example created this profile in /etc/apparmor.d/usr.bin.brave:
# Last Modified: Wed Oct 8 09:29:51 2025
abi <abi/4.0>,
include <tunables/global>
/usr/bin/brave flags=(complain) {
include <abstractions/base>
/usr/bin/brave r,
/usr/bin/env ix,
}
Profiling by Hand
Disable kernel rate limiting:
sysctl -w kernel.printk_ratelimit=0
Lets pick up from our /usr/bin/brave example:
Create a preliminary profile
# Last Modified: Wed Oct 8 09:29:51 2025
abi <abi/4.0>,
include <tunables/global>
/usr/bin/brave flags=(complain) {
include <abstractions/base>
/usr/bin/brave r,
/usr/bin/env ix,
}
Auditd
Linux audit makes your system more secure by providing you the means to analyze what’s going on in your system in great detail. It does not provide any security itself, but instead is useful for tracking these issues and helps you take additional security measures to prevent them.
Install with:
sudo pacman -S audit
- Enable audit at boot-time by setting
audit=1as a kernel parameter, typically either in/etc/kernel/cmdlineor/etc/cmdline.d/security.conffor UKIs.
For example, this is my /etc/cmdline.d/security.conf:
# enable apparmor # enable audit
lsm=landlock,lockdown,yama,integrity,apparmor,bpf audit=1 audit_backlog_limit=256
Create a group to follow principle of least privilege:
sudo groupadd audit-view
sudo usermod -a -G audit-view $USER
In /etc/audit/auditd.conf, change log_group = root to:
log_group = audit-view
Enable:
sudo systemctl enable auditd
sudo systemctl start auditd --now
To create new profiles, auditd should be running. AppArmor can use kernel
audit logs from the userspace auditd daemon, allowing you to build new profiles.
A basic set of rules could be to create a /etc/audit/rules.d/audit.rules with
the following contents:
# Clear existing rules
-D
# Set buffer size
-b 8192
# Monitor /etc/passwd for modifications
-w /etc/passwd -p wa -k passwd_changes
# Monitor sudo command execution
-w /usr/bin/sudo -p x -k sudo_usage
# Enable auditing
-e 1
# Make rules immutable
-e 2
Validate and load the rules, this will populate /etc/audit/audit.rules with
the rules we just set:
sudo augenrules --load
Ensure auditd is running:
sudo systemctl status auditd
Since we set a watch rule for sudo let’s run an update and check the auditd logs:
sudo pacman -Syu
View the logs:
sudo ausearch -k sudo_usage
View the Summary Report:
sudo aureport
sudo aureport --auth
man aureport
Verify the rules are loaded:
sudo auditctl -l
Be careful here, if you enable auditd and don’t iron out the kinks I’ve found
that it freezes after you enter your cryptroot passphrase. If this happens to
you, follow the chroot steps but skip the arch-chroot /mnt step and instead
run:
systemctl --root=/mnt disable auditd
Unmount the partitions, close cryptroot, and Reboot.
Doas over sudo
❗️ Removing sudo may cause compatibility issues with some scripts/tools that expect it, I haven’t had many issues but you should test before completely removing it.
For a more minimalist version of sudo with a smaller codebase and attack
surface, consider doas:
sudo pacman -S opendoas
Create a doas group:
sudo groupadd doas
Add your user to the doas group:
sudo usermod -aG doas $USER
Create /etc/doas.conf with the following contents:
permit setenv {PATH=/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin} :doas
You can add a line below that one like permit nopass your-username as root:
Enabling your user passwordless usage, it’s much less secure but an option.
Alternatively, you can setup the doas persist feature with the following:
permit persist setenv {PATH=/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin} :doas
-
With the above setting, after you successfully authenticate. You won’t be asked for your password for the next 5 minutes. It’s disabled by default because it can be dangerous if used in the wrong environment.
-
You may need to either reboot or do a soft-reset for the groups to take effect.
For yay, you can run:
yay --sudo doas --save
For paru, edit /etc/paru.conf. Near bottom:
Sudo = doas
Edit /etc/mkepkg.conf:
doas hx /etc/makepkg.conf
At the bottom of the file uncomment PACMAN_AUTH=() and add doas:
PACMAN_AUTH=(doas)
Secure the doas.conf:
doas chown -c root:root /etc/doas.conf
doas chmod -c 0400 /etc/doas.conf
doas pacman -Syu
Test and ensure most commands that you use work before removing sudo so you’re aware of potential issues. To benefit from the smaller codebase and attack surface, you have to remove sudo.
doas pacman -R sudo base-devel
Create a symlink replacing sudo with doas:
ln -s $(which doas) /usr/bin/sudo
Now, when you run sudo, doas will be executed. There are some compatibility
issues with this method but not super common.
Intrusion Detection
✔️ Click to Expand AIDE Example
From what I’ve seen, this would work best if you’re running a server or self hosting where your system will be running without you there tweaking settings and AIDE will alert you if anything changes in the meantime.
paru -S aide
AIDE is an intrusion detection system (IDS) that will notify us whenever it detects that a potential intrusion has occurred. When a system is compromised, attackers typically will try to change file permissions and escalate to the root user account and start to modify system files, AIDE can detect this.
To set up AIDE on your system follow these steps:
-
There is a default config at
/etc/aide.conf: -
Initialize the database:
sudo aide -i
You will see in the output of the above command that
AIDE successfully initialized database. New AIDE database written to /var/lib/aide/aide.db.new.gz
- Move the new database and remove the
.new:
sudo mv /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz
- Check the system against the baseline database:
sudo aide -C
- Whenever you make changes to system files, or especially after running a system update or installing new tools, you have to rescan all files to update their checksums in the AIDE database:
sudo aide -u
Unfortunately, AIDE doesn’t automatically replace the old database so you have to rename the new one again:
sudo mv /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz
And finally check again:
sudo aide -C
Start timestamp: 2025-10-02 14:57:49 -0400 (AIDE 0.19.2)
AIDE found NO differences between database and filesystem. Looks okay!!
-
The default settings are fairly strict, I kept getting reports of changes detected because the mtime and ctime of a directory changed. It’s fairly easy to set ignore rules by adding a
!in front of the path.
Create the logfile:
sudo mkdir -p /var/log/aide
sudo touch /var/log/aide/aide.log
Resources
✔️ Click to Expand Resources
-
STIGs are configuration standards developed by the Defense Information Systems Agency (DISA) to secure systems and software for the U.S. Department of Defense (DoD). They are considered a highly authoritative source for system hardening.There are recommendations for hardening all kinds of software in the Stig Viewer
2 + 2
Hardening Networking
NetworkManagers global config is /etc/NetworkManager/NetworkManager.conf.
Additional config files can be placed in /etc/NetworkManager/conf.d.
After editing a config file, apply the changes with:
sudo nmcli general reload
sudo usermod -aG network $USER
Investigate sockets
Display all TCP Sockets with service names:
ss -at
Display all TCP Sockets with port numbers:
ss -atn
Display all UDP Sockets:
ss -au
Configure NetworkManager to use Quad9s DoT
Create /etc/NetworkManager/system-connections/Wi-Fi.nmconnection:
[connection]
dns-over-tls=2
[ipv4]
dns=9.9.9.9#dns.quad9.net;149.112.112.112#dns.quad9.net;
ignore-auto-dns=true
[ipv6]
dns=2620:fe::fe#dns.quad9.net;2620:fe::9#dns.quad9.net;
ignore-auto-dns=true
sudo systemctl restart NetworkManager
Wi-Fi MAC randomization
Add the following to /etc/NetworkManager/conf.d/wifi_rand_mac.conf:
[device-mac-randomization]
# "yes" is already the default for scanning
wifi.scan-rand-mac-address=yes
[connection-mac-randomization]
# Randomize MAC for every ethernet connection
ethernet.cloned-mac-address=random
# Generate a random MAC every time you connect to a wifi network
wifi.cloned-mac-address=random
Unique DUID per connection
Create /etc/NetworkManager/conf.d/duid.conf:
[connection]
ipv6.dhcp-duid=stable-uuid
MAC Address spoofing
This section demonstrates how to spoof your Media Access Control (MAC) address.
Manual MAC randomization with macchanger
Find your device interface and check your current MAC address:
# find interface
ip add
sudo iplink show wlp3s0
The address following "link/ether" is your MAC, bring it down so we can change
it:
sudo ip link set dev wlp3s0 down
# or
# sudo ifconfig wlp3s0 down
Install the macchanger package and run:
sudo macchanger -a wlp3s0
-
-a,--another: Set random vendor MAC of the same kind. -
-r,--random: Set fully random MAC.
Automatically with systemd & macchanger
systemd unit setting a random address while preserving the original NIC vendor bytes.
Create /etc/systemd/system/macspoof@.service:
[Unit]
Description=macchanger on %I
Wants=network-pre.target
Before=network-pre.target
BindsTo=sys-subsystem-net-devices-%i.device
After=sys-subsystem-net-devices-%i.device
[Service]
ExecStart=/usr/bin/macchanger -e %I
Type=oneshot
[Install]
WantedBy=multi-user.target
-
You can use
-rto randomize everything but it’s important that the vendor bytes actually match something and aren’t just random numbers so it’s less recommended. -
The
@.servicepattern allows per-interface instantiation likemacspoof@wlp3s0.service. -
The
%Ivariable automatically resolves to your interface name. -
You only need to append your interface name when enabling the service.
sudo systemctl enable macspoof@wlp3s0.service