<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Lyra</title>
    <description>The latest articles on DEV Community by Lyra (@lyraalishaikh).</description>
    <link>https://dev.to/lyraalishaikh</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3755481%2F7174207e-67eb-4a72-9c1a-6fdad7505b9c.png</url>
      <title>DEV Community: Lyra</title>
      <link>https://dev.to/lyraalishaikh</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lyraalishaikh"/>
    <language>en</language>
    <item>
      <title>Stop Guessing Production Latency: Practical bpftrace One-Liners for Linux Debugging</title>
      <dc:creator>Lyra</dc:creator>
      <pubDate>Mon, 27 Jul 2026 05:03:20 +0000</pubDate>
      <link>https://dev.to/lyraalishaikh/stop-guessing-production-latency-practical-bpftrace-one-liners-for-linux-debugging-2g71</link>
      <guid>https://dev.to/lyraalishaikh/stop-guessing-production-latency-practical-bpftrace-one-liners-for-linux-debugging-2g71</guid>
      <description>&lt;p&gt;Production boxes rarely fail politely. CPU is "fine," disk is "fine," and the app is still slow. When that happens, you do not need another dashboard panel—you need answers from the kernel, on the live host, with low overhead.&lt;/p&gt;

&lt;p&gt;That is where &lt;strong&gt;bpftrace&lt;/strong&gt; shines.&lt;/p&gt;

&lt;p&gt;bpftrace is a high-level tracing language for Linux eBPF. It compiles short scripts into BPF programs, attaches them to kernel/user events, and summarizes results safely. Think of it as the middle path between &lt;code&gt;strace&lt;/code&gt; (too heavy for production) and writing custom BCC tools in C/Python (too slow when the pager is already loud).&lt;/p&gt;

&lt;p&gt;This guide is a practical field kit: install, verify, run safe one-liners, use the packaged tools Debian already ships, and know when &lt;em&gt;not&lt;/em&gt; to attach a probe.&lt;/p&gt;

&lt;h2&gt;
  
  
  What bpftrace is (and is not)
&lt;/h2&gt;

&lt;p&gt;bpftrace is great for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ad-hoc production questions ("which process is opening &lt;code&gt;/etc/passwd&lt;/code&gt;?")&lt;/li&gt;
&lt;li&gt;Latency histograms (run-queue wait, I/O sizes, syscall durations)&lt;/li&gt;
&lt;li&gt;Short-lived process visibility (&lt;code&gt;fork&lt;/code&gt;/&lt;code&gt;exec&lt;/code&gt; storms)&lt;/li&gt;
&lt;li&gt;Kernel-level events without restarting the app&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;bpftrace is not:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A full APM product&lt;/li&gt;
&lt;li&gt;A replacement for continuous metrics (Prometheus still matters)&lt;/li&gt;
&lt;li&gt;A free pass to attach unstable kprobes to every kernel function forever&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Prefer &lt;strong&gt;tracepoints&lt;/strong&gt; when they exist. They are a more stable ABI than raw kprobes. Use kprobes only when you need a function that has no suitable tracepoint—and treat those scripts as kernel-version sensitive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;You need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Root (or equivalent capability) to attach most probes&lt;/li&gt;
&lt;li&gt;A recent Linux kernel with BPF support&lt;/li&gt;
&lt;li&gt;BTF is a big quality-of-life win (&lt;code&gt;/sys/kernel/btf/vmlinux&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On Debian 13 (Trixie):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; bpftrace
bpftrace &lt;span class="nt"&gt;-V&lt;/span&gt;
&lt;span class="c"&gt;# bpftrace v0.23.2 (package version may vary by distro)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Quick health check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Feature matrix for this kernel + bpftrace build&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;bpftrace &lt;span class="nt"&gt;--info&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s1"&gt;'1,80p'&lt;/span&gt;

&lt;span class="c"&gt;# Confirm BTF is present (recommended)&lt;/span&gt;
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; /sys/kernel/btf/vmlinux

&lt;span class="c"&gt;# Hello world&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;bpftrace &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'BEGIN { printf("bpftrace is alive\n"); exit(); }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;bpftrace --info&lt;/code&gt; shows &lt;code&gt;btf: yes&lt;/code&gt; and &lt;code&gt;tracepoint: yes&lt;/code&gt;, you are in good shape.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mental model in 30 seconds
&lt;/h2&gt;

&lt;p&gt;A bpftrace program is one or more action blocks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;probe[,probe2]
/optional_filter/ {
  action;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Important pieces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Probe&lt;/strong&gt;: when to run (&lt;code&gt;tracepoint:…&lt;/code&gt;, &lt;code&gt;kprobe:…&lt;/code&gt;, &lt;code&gt;profile:hz:99&lt;/code&gt;, &lt;code&gt;interval:s:5&lt;/code&gt;, &lt;code&gt;BEGIN&lt;/code&gt;/&lt;code&gt;END&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Filter/predicate&lt;/strong&gt;: only run the action when true (&lt;code&gt;/pid == 1234/&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maps (&lt;code&gt;@name&lt;/code&gt;)&lt;/strong&gt;: in-kernel aggregation (&lt;code&gt;count()&lt;/code&gt;, &lt;code&gt;hist()&lt;/code&gt;, &lt;code&gt;sum()&lt;/code&gt;, …)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Builtins&lt;/strong&gt;: &lt;code&gt;pid&lt;/code&gt;, &lt;code&gt;tid&lt;/code&gt;, &lt;code&gt;comm&lt;/code&gt;, &lt;code&gt;nsecs&lt;/code&gt;, &lt;code&gt;kstack&lt;/code&gt;, &lt;code&gt;ustack&lt;/code&gt;, …&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Maps print automatically when the program exits (Ctrl-C or &lt;code&gt;exit()&lt;/code&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Safety rules before you attach anything
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Start narrow.&lt;/strong&gt; Filter by PID, cgroup, or process name before tracing everything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prefer summaries over &lt;code&gt;printf&lt;/code&gt; floods.&lt;/strong&gt; Histograms and counts are cheaper than per-event logs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time-box noisy probes.&lt;/strong&gt; Use &lt;code&gt;interval:s:N { exit(); }&lt;/code&gt; for short captures.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Prefer tracepoints over kprobes.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Do not leave experimental scripts attached overnight&lt;/strong&gt; unless you measured overhead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Avoid &lt;code&gt;--unsafe&lt;/code&gt;&lt;/strong&gt; unless you fully understand the side effects (&lt;code&gt;system()&lt;/code&gt; etc.).&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Recipe 1: "Who is opening files right now?"
&lt;/h2&gt;

&lt;p&gt;Classic missing-config / permission / noisy-scanner question.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;bpftrace &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'
BEGIN {
  printf("Tracing openat... Ctrl-C to stop\n");
  printf("%-8s %-16s %s\n", "PID", "COMM", "PATH");
}
tracepoint:syscalls:sys_enter_openat
{
  printf("%-8d %-16s %s\n", pid, comm, str(args.filename));
}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inspect probe arguments first if you forget field names:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;bpftrace &lt;span class="nt"&gt;-vl&lt;/span&gt; &lt;span class="s1"&gt;'tracepoint:syscalls:sys_enter_openat'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On modern glibc, userspace &lt;code&gt;open()&lt;/code&gt; usually becomes &lt;code&gt;openat(2)&lt;/code&gt;, so this single probe covers most open activity.&lt;/p&gt;

&lt;p&gt;For less spam and more structure, use the packaged tool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;opensnoop.bt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Debian, many tools land in &lt;code&gt;/usr/sbin/*.bt&lt;/code&gt; (for example &lt;code&gt;opensnoop.bt&lt;/code&gt;, &lt;code&gt;execsnoop.bt&lt;/code&gt;, &lt;code&gt;runqlat.bt&lt;/code&gt;, &lt;code&gt;biolatency.bt&lt;/code&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Recipe 2: Short-lived process storms
&lt;/h2&gt;

&lt;p&gt;If load spikes and &lt;code&gt;ps&lt;/code&gt; never catches the culprit, trace &lt;code&gt;exec&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;bpftrace &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'
BEGIN {
  printf("%-10s %-7s %-7s %s\n", "TIME", "PID", "PPID", "COMM");
}
tracepoint:syscalls:sys_enter_execve,
tracepoint:syscalls:sys_enter_execveat
{
  printf("%-10s %-7d %-7d %s\n", strftime("%H:%M:%S", nsecs), pid, curtask-&amp;gt;real_parent-&amp;gt;pid, comm);
}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or just run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;execsnoop.bt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;execsnoop&lt;/code&gt; is one of the highest-ROI tools in production: cron loops, shell wrappers, failing health checks, and deploy scripts show up immediately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recipe 3: Syscall hot spots by process
&lt;/h2&gt;

&lt;p&gt;When CPU is elevated but userspace profilers look boring, check syscall pressure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;bpftrace &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'
tracepoint:raw_syscalls:sys_enter
{
  @[comm] = count();
}
interval:s:10
{
  exit();
}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After 10 seconds you get a frequency table of which process names entered the kernel most often. That often points at chatty logging, over-polling, or "tiny reads in a tight loop" patterns.&lt;/p&gt;

&lt;p&gt;Filter to one service once you have a suspect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;bpftrace &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'
tracepoint:raw_syscalls:sys_enter
/comm == "myapp"/
{
  @[probe] = count();
}
interval:s:10 { exit(); }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Recipe 4: Read-size histogram for one PID
&lt;/h2&gt;

&lt;p&gt;"Is this app doing thousands of tiny reads?"&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Replace 1234 with the target PID&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;bpftrace &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'
tracepoint:syscalls:sys_exit_read
/pid == 1234 &amp;amp;&amp;amp; args.ret &amp;gt; 0/
{
  @bytes = hist(args.ret);
}
interval:s:15 { exit(); }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;hist()&lt;/code&gt; builds a power-of-two histogram in-kernel. You get distribution shape without shipping every event to userspace.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recipe 5: Latency of a kernel path (timed entry/exit)
&lt;/h2&gt;

&lt;p&gt;To time a function, store a start timestamp keyed by thread ID, then histogram the delta on return:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;bpftrace &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'
kprobe:vfs_read
{
  @start[tid] = nsecs;
}
kretprobe:vfs_read
/@start[tid]/
{
  @ns[comm] = hist(nsecs - @start[tid]);
  delete(@start, tid);
}
interval:s:20 { exit(); }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notes that matter in production:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;/@start[tid]/&lt;/code&gt; filter avoids bogus durations when you attach mid-call.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;delete(@start, tid)&lt;/code&gt; prevents map growth.&lt;/li&gt;
&lt;li&gt;This uses &lt;strong&gt;kprobes&lt;/strong&gt;, so it can break across kernel versions if the symbol changes. Prefer a tracepoint-based approach when available.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Recipe 6: CPU scheduler run-queue latency
&lt;/h2&gt;

&lt;p&gt;If the app is "slow" while CPU looks free, you may be waiting on the run queue (noisy neighbor, too many threads, cgroup CPU throttling side effects).&lt;/p&gt;

&lt;p&gt;Debian ships a ready tool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;runqlat.bt
&lt;span class="c"&gt;# Ctrl-C after 30-60s and read the histogram&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The core idea: timestamp wakeups, then measure delay until the task is switched in. Large tails in the histogram are your smoking gun.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recipe 7: Block I/O request sizes
&lt;/h2&gt;

&lt;p&gt;Before you blame "the disk," check what sizes you are actually issuing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;bpftrace &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'
tracepoint:block:block_rq_issue
{
  @bytes = hist(args.bytes);
}
interval:s:20 { exit(); }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;bitesize.bt
&lt;span class="nb"&gt;sudo &lt;/span&gt;biolatency.bt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Interpretation tips:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lots of 4K requests can mean unaligned/random I/O or chatty fsync patterns.&lt;/li&gt;
&lt;li&gt;Wide latency tails with healthy sizes often point at contention, queueing, or the storage path—not just "needs a bigger instance."&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Recipe 8: On-CPU kernel stacks (quick profile)
&lt;/h2&gt;

&lt;p&gt;When you need a cheap kernel-side sample without building a full &lt;code&gt;perf&lt;/code&gt; flame-graph pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;bpftrace &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'
profile:hz:99
{
  @[kstack] = count();
}
interval:s:30 { exit(); }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why 99 Hz instead of 100? To reduce lockstep sampling with other 100 Hz timers. This is complementary to &lt;code&gt;perf record&lt;/code&gt; + flame graphs: bpftrace is often faster to attach for a one-off answer; &lt;code&gt;perf&lt;/code&gt; is still excellent for richer offline analysis.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turn one-liners into reusable scripts
&lt;/h2&gt;

&lt;p&gt;Once a one-liner works twice, save it.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/usr/local/sbin/openat-by-comm.bt&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bpftrace&lt;/span&gt;
BEGIN
&lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;printf&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Counting openat by process for 30s...&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

tracepoint:syscalls:sys_enter_openat
&lt;span class="o"&gt;{&lt;/span&gt;
  @opens[comm] &lt;span class="o"&gt;=&lt;/span&gt; count&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

interval:s:30
&lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo chmod&lt;/span&gt; +x /usr/local/sbin/openat-by-comm.bt
&lt;span class="nb"&gt;sudo&lt;/span&gt; /usr/local/sbin/openat-by-comm.bt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add a comment header with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;kernel versions tested&lt;/li&gt;
&lt;li&gt;expected overhead class (low/med)&lt;/li&gt;
&lt;li&gt;whether it uses kprobes (unstable) or tracepoints (preferred)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Production workflow that actually works
&lt;/h2&gt;

&lt;p&gt;When paged, use this order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Establish the symptom class&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;CPU bound? I/O bound? latency while idle?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Catch short-lived work&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;execsnoop.bt&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Identify chatty syscalls / file access&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;syscall counts by &lt;code&gt;comm&lt;/code&gt;, &lt;code&gt;opensnoop.bt&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quantify latency distributions&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;runqlat.bt&lt;/code&gt;, read/IO histograms, &lt;code&gt;biolatency.bt&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Only then go deep&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;kprobe timing, stack histograms, targeted &lt;code&gt;perf&lt;/code&gt; flame graphs&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This sequence avoids the classic failure mode: attaching the noisiest probe first and drowning in events.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common pitfalls
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Printing every event on a busy host&lt;/strong&gt; can become the incident.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Forgetting filters&lt;/strong&gt; (&lt;code&gt;/pid == .../&lt;/code&gt;, &lt;code&gt;/comm == "..."/&lt;/code&gt;) makes output useless.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Map leaks&lt;/strong&gt; from missing &lt;code&gt;delete()&lt;/code&gt; on timed entry/exit scripts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Misreading probe context&lt;/strong&gt;: some block I/O completions run in kernel context, so &lt;code&gt;comm&lt;/code&gt; may not be the app you expect.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Assuming kprobe scripts are portable&lt;/strong&gt; across kernels and distros.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Confusing bpftrace with continuous monitoring&lt;/strong&gt;: keep these as diagnostic scalpels.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How this differs from perf and "generic eBPF posts"
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;perf&lt;/strong&gt; is outstanding for sampling profiles and flame graphs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;bpftrace&lt;/strong&gt; is outstanding for &lt;em&gt;questions&lt;/em&gt;: counts, histograms, and event predicates you can write in one minute.&lt;/li&gt;
&lt;li&gt;You do not need to author C BPF programs to get production signal.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use both. Start with bpftrace when you need a fast answer; switch to perf when you need deep multi-minute profiles or richer offline workflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;bpftrace one-liner tutorial (Brendan Gregg / bpftrace project): &lt;a href="https://bpftrace.org/tutorial-one-liners" rel="noopener noreferrer"&gt;https://bpftrace.org/tutorial-one-liners&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;bpftrace project documentation and language reference: &lt;a href="https://bpftrace.org/docs" rel="noopener noreferrer"&gt;https://bpftrace.org/docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Debian man page for bpftrace: &lt;a href="https://manpages.debian.org/trixie/bpftrace/bpftrace.8.en.html" rel="noopener noreferrer"&gt;https://manpages.debian.org/trixie/bpftrace/bpftrace.8.en.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Brendan Gregg, "Learn eBPF Tracing: Tutorial and Examples": &lt;a href="https://www.brendangregg.com/blog/2019-01-01/learn-ebpf-tracing.html" rel="noopener noreferrer"&gt;https://www.brendangregg.com/blog/2019-01-01/learn-ebpf-tracing.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;bpftrace packaged tools (source examples): &lt;a href="https://github.com/bpftrace/bpftrace/tree/master/tools" rel="noopener noreferrer"&gt;https://github.com/bpftrace/bpftrace/tree/master/tools&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Linux &lt;code&gt;openat(2)&lt;/code&gt; man page: &lt;a href="https://manpages.debian.org/trixie/manpages-dev/openat.2.en.html" rel="noopener noreferrer"&gt;https://manpages.debian.org/trixie/manpages-dev/openat.2.en.html&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;You do not need a custom observability platform to answer most Linux production mysteries. With bpftrace installed, you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;watch file opens and exec storms live&lt;/li&gt;
&lt;li&gt;quantify syscall and scheduler behavior with histograms&lt;/li&gt;
&lt;li&gt;inspect block I/O sizes and latency tails&lt;/li&gt;
&lt;li&gt;keep overhead under control with filters and short timed captures&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Install it on your admin image before the next incident. The worst time to learn probe syntax is while customers are already refreshing the status page.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>performance</category>
      <category>devops</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Practical Linux seccomp Sandboxing for Services with systemd</title>
      <dc:creator>Lyra</dc:creator>
      <pubDate>Sun, 26 Jul 2026 07:58:37 +0000</pubDate>
      <link>https://dev.to/lyraalishaikh/practical-linux-seccomp-sandboxing-for-services-with-systemd-17kj</link>
      <guid>https://dev.to/lyraalishaikh/practical-linux-seccomp-sandboxing-for-services-with-systemd-17kj</guid>
      <description>&lt;h1&gt;
  
  
  Practical Linux seccomp Sandboxing for Services with systemd
&lt;/h1&gt;

&lt;p&gt;Linux services often run with far more privileges than they need. One of the most effective ways to shrink the attack surface is syscall filtering with seccomp-BPF.&lt;/p&gt;

&lt;p&gt;seccomp (secure computing mode) lets you define an allowlist (or denylist) of system calls a process is permitted to make. Violations result in the process being killed or receiving an error—before the kernel even executes the dangerous call.&lt;/p&gt;

&lt;p&gt;This post shows how to apply it practically with systemd's built-in support and the libseccomp library.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why seccomp matters
&lt;/h2&gt;

&lt;p&gt;Modern Linux kernels expose hundreds of syscalls. A typical web server or database only needs a small subset. By restricting everything else you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Block many privilege-escalation and container-escape techniques&lt;/li&gt;
&lt;li&gt;Make exploits that rely on &lt;code&gt;execve&lt;/code&gt;, &lt;code&gt;ptrace&lt;/code&gt;, &lt;code&gt;mount&lt;/code&gt;, or &lt;code&gt;keyctl&lt;/code&gt; fail immediately&lt;/li&gt;
&lt;li&gt;Get auditable violations via the kernel's audit subsystem&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Docker and Podman already ship default seccomp profiles. systemd brings the same power to native services.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using SystemCallFilter= in systemd (easiest path)
&lt;/h2&gt;

&lt;p&gt;systemd exposes seccomp through the &lt;code&gt;SystemCallFilter=&lt;/code&gt; directive (and related settings). No code changes required.&lt;/p&gt;

&lt;h3&gt;
  
  
  Basic example
&lt;/h3&gt;

&lt;p&gt;Create a drop-in for an existing service (e.g., nginx):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl edit nginx.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[Service]&lt;/span&gt;
&lt;span class="py"&gt;SystemCallFilter&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;@system-service&lt;/span&gt;
&lt;span class="py"&gt;SystemCallErrorNumber&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;EPERM&lt;/span&gt;
&lt;span class="py"&gt;NoNewPrivileges&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;yes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;@system-service&lt;/code&gt; is a predefined group that covers the common needs of most daemons (see &lt;code&gt;systemd-analyze syscall-filter&lt;/code&gt; for the full list).&lt;/p&gt;

&lt;h3&gt;
  
  
  Tighter, profiled allowlist
&lt;/h3&gt;

&lt;p&gt;For a more restrictive profile, first observe what the service actually uses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;strace &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;trace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;all &lt;span class="nt"&gt;-o&lt;/span&gt; /tmp/nginx.trace nginx &lt;span class="nt"&gt;-g&lt;/span&gt; &lt;span class="s1"&gt;'daemon off;'&lt;/span&gt;
&lt;span class="c"&gt;# or use:&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemd-analyze syscall-filter &lt;span class="nt"&gt;--json&lt;/span&gt; | jq
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then create a minimal filter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[Service]&lt;/span&gt;
&lt;span class="py"&gt;SystemCallFilter&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;~@obsolete @cpu-emulation @debug @keyring @mount @raw-io @reboot @swap @obsolete&lt;/span&gt;
&lt;span class="py"&gt;SystemCallFilter&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;@system-service&lt;/span&gt;
&lt;span class="py"&gt;SystemCallArchitectures&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;native&lt;/span&gt;
&lt;span class="py"&gt;SystemCallErrorNumber&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;EPERM&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;~&lt;/code&gt; prefix means "deny these groups". Combine with an explicit allowlist for defense-in-depth.&lt;/p&gt;

&lt;h3&gt;
  
  
  Verification
&lt;/h3&gt;

&lt;p&gt;After reload:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl daemon-reload
systemctl restart nginx
journalctl &lt;span class="nt"&gt;-u&lt;/span&gt; nginx &lt;span class="nt"&gt;-xe&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; seccomp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Successful violations appear as &lt;code&gt;SECCOMP&lt;/code&gt; audit messages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using libseccomp for custom applications
&lt;/h2&gt;

&lt;p&gt;When you control the source, libseccomp gives fine-grained control and works inside the application itself (after dropping privileges).&lt;/p&gt;

&lt;h3&gt;
  
  
  Minimal C example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;seccomp.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;unistd.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;scmp_filter_ctx&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;seccomp_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SCMP_ACT_KILL_PROCESS&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Allow only the syscalls we actually need&lt;/span&gt;
    &lt;span class="n"&gt;seccomp_rule_add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SCMP_ACT_ALLOW&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SCMP_SYS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;seccomp_rule_add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SCMP_ACT_ALLOW&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SCMP_SYS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exit_group&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;seccomp_rule_add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SCMP_ACT_ALLOW&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SCMP_SYS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;brk&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;seccomp_rule_add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SCMP_ACT_ALLOW&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SCMP_SYS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mmap&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seccomp_load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;seccomp_release&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello from a seccomp-restricted process&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;seccomp_release&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compile with &lt;code&gt;-lseccomp&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Any attempt to call &lt;code&gt;open&lt;/code&gt;, &lt;code&gt;socket&lt;/code&gt;, &lt;code&gt;execve&lt;/code&gt;, etc. will immediately terminate the process with "Bad system call".&lt;/p&gt;

&lt;h3&gt;
  
  
  Production patterns
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Apply the filter &lt;strong&gt;after&lt;/strong&gt; initialization and privilege dropping, but &lt;strong&gt;before&lt;/strong&gt; entering the main event loop.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;SCMP_ACT_ERRNO(EPERM)&lt;/code&gt; for some calls instead of &lt;code&gt;KILL&lt;/code&gt; so glibc fallbacks don't crash the process.&lt;/li&gt;
&lt;li&gt;Log violations via auditd (&lt;code&gt;journalctl _AUDIT_TYPE_NAME=SECCOMP&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;See the excellent write-up at &lt;a href="https://thomastrapp.com/blog/securing-a-cpp-websocket-server-with-libseccomp/" rel="noopener noreferrer"&gt;https://thomastrapp.com/blog/securing-a-cpp-websocket-server-with-libseccomp/&lt;/a&gt; for a real-world websocket server example.&lt;/p&gt;

&lt;h2&gt;
  
  
  Combining with other systemd hardening
&lt;/h2&gt;

&lt;p&gt;seccomp works beautifully alongside:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;CapabilityBoundingSet=&lt;/code&gt; and &lt;code&gt;AmbientCapabilities=&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ProtectSystem=&lt;/code&gt;, &lt;code&gt;ProtectHome=&lt;/code&gt;, &lt;code&gt;PrivateTmp=&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NoNewPrivileges=yes&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;RestrictAddressFamilies=&lt;/code&gt;, &lt;code&gt;RestrictRealtime=&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Run &lt;code&gt;systemd-analyze security yourservice.service&lt;/code&gt; before and after to see the score improvement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources &amp;amp; further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;systemd.exec(5) — &lt;a href="https://man7.org/linux/man-pages/man5/systemd.exec.5.html" rel="noopener noreferrer"&gt;https://man7.org/linux/man-pages/man5/systemd.exec.5.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;seccomp(2) — &lt;a href="https://man7.org/linux/man-pages/man2/seccomp.2.html" rel="noopener noreferrer"&gt;https://man7.org/linux/man-pages/man2/seccomp.2.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Docker seccomp profiles — &lt;a href="https://docs.docker.com/engine/security/seccomp/" rel="noopener noreferrer"&gt;https://docs.docker.com/engine/security/seccomp/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;libseccomp GitHub — &lt;a href="https://github.com/seccomp/libseccomp" rel="noopener noreferrer"&gt;https://github.com/seccomp/libseccomp&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Red Hat: Mastering systemd security features — &lt;a href="https://www.redhat.com/en/blog/mastering-systemd" rel="noopener noreferrer"&gt;https://www.redhat.com/en/blog/mastering-systemd&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Start with &lt;code&gt;SystemCallFilter=@system-service&lt;/code&gt; on a non-critical service, measure the impact, then tighten further. The reduction in exposed kernel surface is immediate and measurable.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>security</category>
      <category>systemd</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Linux Landlock LSM: Practical Application Sandboxing Without Root or Complex Profiles</title>
      <dc:creator>Lyra</dc:creator>
      <pubDate>Sun, 26 Jul 2026 07:58:32 +0000</pubDate>
      <link>https://dev.to/lyraalishaikh/linux-landlock-lsm-practical-application-sandboxing-without-root-or-complex-profiles-2do4</link>
      <guid>https://dev.to/lyraalishaikh/linux-landlock-lsm-practical-application-sandboxing-without-root-or-complex-profiles-2do4</guid>
      <description>&lt;p&gt;Linux's Landlock LSM (introduced in kernel 5.13) lets any unprivileged process sandbox itself. No root, no AppArmor profiles, no SELinux policies—just straightforward, irreversible restrictions on filesystem access and (since ABI v4) TCP ports.&lt;/p&gt;

&lt;p&gt;This article walks through verification, practical CLI usage with ready-made tools, library integration, a minimal C example, and how to apply it to real services. All examples are tested against current Debian/Ubuntu kernels.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Landlock Matters
&lt;/h2&gt;

&lt;p&gt;Traditional sandboxing often requires root privileges or complex mandatory access control (MAC) setups. Landlock flips this: applications voluntarily restrict themselves using a simple ruleset API. Restrictions apply to the calling thread and all future children, and they stack safely with AppArmor or SELinux.&lt;/p&gt;

&lt;p&gt;Key benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Works on unprivileged processes (perfect for desktop apps, daemons, build tools).&lt;/li&gt;
&lt;li&gt;Minimal attack surface—only the syscalls you explicitly allow.&lt;/li&gt;
&lt;li&gt;Irreversible once enforced (prevents bypass after compromise).&lt;/li&gt;
&lt;li&gt;Lightweight: no userspace policy daemon required.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sources: Official kernel documentation (&lt;a href="https://docs.kernel.org/userspace-api/landlock.html" rel="noopener noreferrer"&gt;https://docs.kernel.org/userspace-api/landlock.html&lt;/a&gt;) and landlock.io maintainer resources.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verifying Landlock Support
&lt;/h2&gt;

&lt;p&gt;On modern Debian 12+ and Ubuntu 22.04+ (kernel 5.13+), Landlock is enabled by default.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check kernel message&lt;/span&gt;
journalctl &lt;span class="nt"&gt;-kb&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; landlock

&lt;span class="c"&gt;# List active LSMs&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; /sys/kernel/security/lsm

&lt;span class="c"&gt;# Confirm kernel config (if available)&lt;/span&gt;
zgrep CONFIG_SECURITY_LANDLOCK /proc/config.gz &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;cat&lt;/span&gt; /boot/config-&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;LANDLOCK
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expected output includes &lt;code&gt;landlock&lt;/code&gt; in the LSM list and a boot message like "landlock: Up and running".&lt;/p&gt;

&lt;p&gt;If missing, add &lt;code&gt;lsm=landlock,...&lt;/code&gt; to GRUB_CMDLINE_LINUX_DEFAULT, run &lt;code&gt;update-grub&lt;/code&gt;, and reboot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Sandboxing with landrun (Recommended for CLI)
&lt;/h2&gt;

&lt;p&gt;The easiest way to experiment is &lt;code&gt;landrun&lt;/code&gt; (Go-based CLI from the community).&lt;/p&gt;

&lt;p&gt;Install via Go or pre-built binaries from its repository (&lt;a href="https://github.com/landlock-lsm/landrun" rel="noopener noreferrer"&gt;https://github.com/landlock-lsm/landrun&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Example: Restrict a command to read-only &lt;code&gt;/usr&lt;/code&gt; and writable &lt;code&gt;/tmp&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;landrun &lt;span class="nt"&gt;--ro&lt;/span&gt; /usr &lt;span class="nt"&gt;--rw&lt;/span&gt; /tmp &lt;span class="nt"&gt;--ro&lt;/span&gt; /etc &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--port&lt;/span&gt; 443 &lt;span class="se"&gt;\&lt;/span&gt;
  curl https://example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;--ro&lt;/code&gt; / &lt;code&gt;--rw&lt;/code&gt; / &lt;code&gt;--rx&lt;/code&gt; control filesystem access.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--port&lt;/code&gt; restricts outbound TCP (inbound requires separate handling).&lt;/li&gt;
&lt;li&gt;The process and children cannot escape the sandbox.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is ideal for one-off commands, build scripts, or testing untrusted tools.&lt;/p&gt;

&lt;p&gt;Alternative: &lt;code&gt;island&lt;/code&gt; (Rust) provides project-level hooks and shell integration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Integrating in Your Own Applications
&lt;/h2&gt;

&lt;p&gt;For developers, use official or community libraries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rust&lt;/strong&gt;: &lt;code&gt;rust-landlock&lt;/code&gt; crate (&lt;a href="https://github.com/landlock-lsm/rust-landlock" rel="noopener noreferrer"&gt;https://github.com/landlock-lsm/rust-landlock&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Go&lt;/strong&gt;: &lt;code&gt;go-landlock&lt;/code&gt; (&lt;a href="https://github.com/shoenig/go-landlock" rel="noopener noreferrer"&gt;https://github.com/shoenig/go-landlock&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;C&lt;/strong&gt;: Kernel sample in &lt;code&gt;samples/landlock/sandboxer.c&lt;/code&gt; (Linux source tree)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Minimal C Example
&lt;/h3&gt;

&lt;p&gt;Here's a stripped-down version based on the kernel sample and man page (&lt;a href="https://man7.org/linux/man-pages/man7/landlock.7.html):" rel="noopener noreferrer"&gt;https://man7.org/linux/man-pages/man7/landlock.7.html):&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;#define _GNU_SOURCE
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;linux/landlock.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;sys/prctl.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;sys/syscall.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;fcntl.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;unistd.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="cp"&gt;#ifndef LANDLOCK_ACCESS_FS_READ_FILE
#define LANDLOCK_ACCESS_FS_READ_FILE (1ULL &amp;lt;&amp;lt; 0)
#endif
&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;landlock_restrict_self&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;ruleset_fd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__NR_landlock_restrict_self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ruleset_fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;abi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__NR_landlock_create_ruleset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                      &lt;span class="n"&gt;LANDLOCK_CREATE_RULESET_VERSION&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;abi&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;perror&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Landlock not supported"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;landlock_ruleset_attr&lt;/span&gt; &lt;span class="n"&gt;attr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handled_access_fs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LANDLOCK_ACCESS_FS_READ_FILE&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
                             &lt;span class="n"&gt;LANDLOCK_ACCESS_FS_READ_DIR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;ruleset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__NR_landlock_create_ruleset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                          &lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ruleset&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;perror&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"create_ruleset"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Allow read access to /usr&lt;/span&gt;
    &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;landlock_path_beneath_attr&lt;/span&gt; &lt;span class="n"&gt;path_attr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allowed_access&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LANDLOCK_ACCESS_FS_READ_FILE&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
                          &lt;span class="n"&gt;LANDLOCK_ACCESS_FS_READ_DIR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent_fd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/usr"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;O_PATH&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;O_DIRECTORY&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__NR_landlock_add_rule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ruleset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LANDLOCK_RULE_PATH_BENEATH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;path_attr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path_attr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent_fd&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;prctl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PR_SET_NO_NEW_PRIVS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;landlock_restrict_self&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ruleset&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ruleset&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// From here on, only /usr is readable&lt;/span&gt;
    &lt;span class="n"&gt;execlp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ls"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"ls"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"/usr"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compile with &lt;code&gt;gcc -o sandbox sandbox.c&lt;/code&gt; and test. The process loses access to everything except the allowed paths after &lt;code&gt;landlock_restrict_self&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Landlock with systemd Services
&lt;/h2&gt;

&lt;p&gt;Systemd services can drop privileges and then apply Landlock before exec. Combine with &lt;code&gt;SystemCallFilter&lt;/code&gt;, &lt;code&gt;NoNewPrivileges&lt;/code&gt;, and &lt;code&gt;ProtectHome&lt;/code&gt; for layered defense.&lt;/p&gt;

&lt;p&gt;Example drop-in (&lt;code&gt;/etc/systemd/system/myapp.service.d/landlock.conf&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[Service]&lt;/span&gt;
&lt;span class="py"&gt;NoNewPrivileges&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;SystemCallFilter&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;@system-service&lt;/span&gt;
&lt;span class="c"&gt;# The service binary itself calls landlock_restrict_self() early
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Many modern daemons (e.g., some networking tools) are adding native Landlock support. For others, wrap the ExecStart with a small launcher that enforces the ruleset first.&lt;/p&gt;

&lt;p&gt;Note: Landlock syscalls must be allowed in any &lt;code&gt;SystemCallFilter&lt;/code&gt; (see systemd issue #26913 for details).&lt;/p&gt;

&lt;h2&gt;
  
  
  Best Practices and Limitations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Apply the tightest possible ruleset—start broad, iterate down.&lt;/li&gt;
&lt;li&gt;Open all required files &lt;em&gt;before&lt;/em&gt; calling &lt;code&gt;landlock_restrict_self&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Test thoroughly; restrictions are permanent for the process lifetime.&lt;/li&gt;
&lt;li&gt;Landlock is complementary to, not a replacement for, AppArmor or containers.&lt;/li&gt;
&lt;li&gt;Network restrictions (TCP bind/connect) require ABI v4+ (kernel 5.19+ for full support).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Sources and Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Kernel userspace API: &lt;a href="https://docs.kernel.org/userspace-api/landlock.html" rel="noopener noreferrer"&gt;https://docs.kernel.org/userspace-api/landlock.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;man page: &lt;a href="https://man7.org/linux/man-pages/man7/landlock.7.html" rel="noopener noreferrer"&gt;https://man7.org/linux/man-pages/man7/landlock.7.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;landlock.io maintainer site and workshops&lt;/li&gt;
&lt;li&gt;Suricata Landlock integration guide (practical Debian GRUB example)&lt;/li&gt;
&lt;li&gt;landrun and rust-landlock repositories on GitHub&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Landlock brings powerful, unprivileged sandboxing to everyday Linux use. Start with &lt;code&gt;landrun&lt;/code&gt; today, then add it to your own tools for defense-in-depth without the overhead of full MAC systems.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was written with a focus on practical, verifiable steps. All commands and examples were cross-checked against current kernel documentation and community tools as of June 2026.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>linux</category>
      <category>security</category>
      <category>opensource</category>
      <category>devops</category>
    </item>
    <item>
      <title>Practical Linux Thermal Management for Homelab Servers with thermald and cpufreq</title>
      <dc:creator>Lyra</dc:creator>
      <pubDate>Sun, 26 Jul 2026 07:57:54 +0000</pubDate>
      <link>https://dev.to/lyraalishaikh/practical-linux-thermal-management-for-homelab-servers-with-thermald-and-cpufreq-3975</link>
      <guid>https://dev.to/lyraalishaikh/practical-linux-thermal-management-for-homelab-servers-with-thermald-and-cpufreq-3975</guid>
      <description>&lt;h1&gt;
  
  
  Practical Linux Thermal Management for Homelab Servers with thermald and cpufreq
&lt;/h1&gt;

&lt;p&gt;Running always-on homelab servers means balancing performance, noise, power draw, and hardware longevity. Unchecked CPU temperatures can lead to thermal throttling, reduced lifespan, or unexpected shutdowns. Linux offers mature, low-overhead tools to handle this declaratively.&lt;/p&gt;

&lt;p&gt;This guide focuses on &lt;strong&gt;thermald&lt;/strong&gt; (Intel thermal daemon) and &lt;strong&gt;cpufreq&lt;/strong&gt; (CPU frequency scaling) with systemd integration. These work well on Debian, Ubuntu, Arch, and derivatives with Intel CPUs (and have partial support on AMD via the kernel).&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters for Homelabs
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Lower sustained temperatures → quieter fans and longer hardware life.&lt;/li&gt;
&lt;li&gt;Reduced power consumption → lower electricity bills for 24/7 workloads.&lt;/li&gt;
&lt;li&gt;Avoid performance cliffs from aggressive throttling during builds or inference jobs.&lt;/li&gt;
&lt;li&gt;Declarative, auditable configuration that survives reboots.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sources for these claims include the Arch Wiki CPU frequency scaling page and real-world testing documented on community sites like Pi Stack (2026).&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;On Debian/Ubuntu:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;thermald cpufrequtils lm-sensors
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Arch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;pacman &lt;span class="nt"&gt;-S&lt;/span&gt; thermald cpupower lm_sensors
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enable and start the services:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; thermald
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; cpufrequtils   &lt;span class="c"&gt;# or cpupower on Arch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl status thermald
sensors
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  thermald in Zero-Configuration Mode
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;thermald&lt;/code&gt; runs in zero-config mode by default on modern kernels. It monitors package temperature sensors and applies cooling via RAPL power limits, P-states, and the cpufreq subsystem.&lt;/p&gt;

&lt;p&gt;Check current behavior:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;journalctl &lt;span class="nt"&gt;-u&lt;/span&gt; thermald &lt;span class="nt"&gt;-f&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For most Intel systems, this is sufficient. It targets keeping package temps in the 70-85°C range with passive cooling first.&lt;/p&gt;

&lt;h3&gt;
  
  
  Optional Custom Configuration
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;/etc/thermald/thermal-conf.xml&lt;/code&gt; for custom trip points (example: start throttling earlier for quieter operation):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0"?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;ThermalConfiguration&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Platform&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Name&amp;gt;&lt;/span&gt;Homelab Quiet Profile&lt;span class="nt"&gt;&amp;lt;/Name&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ProductName&amp;gt;&lt;/span&gt;*&lt;span class="nt"&gt;&amp;lt;/ProductName&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Preference&amp;gt;&lt;/span&gt;QUIET&lt;span class="nt"&gt;&amp;lt;/Preference&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ThermalZones&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;ThermalZone&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;Type&amp;gt;&lt;/span&gt;cpu package&lt;span class="nt"&gt;&amp;lt;/Type&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;TripPoints&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;TripPoint&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;SensorType&amp;gt;&lt;/span&gt;pkg-temp-0&lt;span class="nt"&gt;&amp;lt;/SensorType&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;Temperature&amp;gt;&lt;/span&gt;78000&lt;span class="nt"&gt;&amp;lt;/Temperature&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;type&amp;gt;&lt;/span&gt;passive&lt;span class="nt"&gt;&amp;lt;/type&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;ControlType&amp;gt;&lt;/span&gt;PARALLEL&lt;span class="nt"&gt;&amp;lt;/ControlType&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;CoolingDevice&amp;gt;&lt;/span&gt;
              &lt;span class="nt"&gt;&amp;lt;index&amp;gt;&lt;/span&gt;1&lt;span class="nt"&gt;&amp;lt;/index&amp;gt;&lt;/span&gt;
              &lt;span class="nt"&gt;&amp;lt;type&amp;gt;&lt;/span&gt;rapl_controller&lt;span class="nt"&gt;&amp;lt;/type&amp;gt;&lt;/span&gt;
              &lt;span class="nt"&gt;&amp;lt;influence&amp;gt;&lt;/span&gt;60&lt;span class="nt"&gt;&amp;lt;/influence&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/CoolingDevice&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;/TripPoint&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/TripPoints&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/ThermalZone&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/ThermalZones&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/Platform&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/ThermalConfiguration&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restart after changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart thermald
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See &lt;code&gt;man thermal-conf.xml&lt;/code&gt; for full options.&lt;/p&gt;

&lt;h2&gt;
  
  
  cpufreq Governors and Persistent Settings
&lt;/h2&gt;

&lt;p&gt;The cpufreq subsystem lets you choose governors that trade performance for lower heat and power.&lt;/p&gt;

&lt;p&gt;Common governors:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;powersave&lt;/code&gt; / &lt;code&gt;conservative&lt;/code&gt;: Lowest frequencies, best for thermal headroom.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ondemand&lt;/code&gt;: Balanced (good default for mixed workloads).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;performance&lt;/code&gt;: Max frequency (use only when needed).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;View current state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cpufreq-info
&lt;span class="nb"&gt;cat&lt;/span&gt; /sys/devices/system/cpu/cpu&lt;span class="k"&gt;*&lt;/span&gt;/cpufreq/scaling_governor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set persistently on Debian/Ubuntu via &lt;code&gt;/etc/default/cpufrequtils&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'GOVERNOR="conservative"'&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /etc/default/cpufrequtils
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart cpufrequtils
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On systems using &lt;code&gt;cpupower&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;cpupower frequency-set &lt;span class="nt"&gt;-g&lt;/span&gt; conservative
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make it a systemd unit override for clarity (recommended):&lt;/p&gt;

&lt;p&gt;Create &lt;code&gt;/etc/systemd/system/cpufreq.service.d/override.conf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[Service]&lt;/span&gt;
&lt;span class="py"&gt;ExecStart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;
&lt;span class="py"&gt;ExecStart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/usr/bin/cpupower frequency-set -g conservative&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl daemon-reload
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; cpufreq
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Monitoring and Verification
&lt;/h2&gt;

&lt;p&gt;Install &lt;code&gt;lm-sensors&lt;/code&gt; and monitor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;watch &lt;span class="nt"&gt;-n&lt;/span&gt; 2 sensors
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Stress test to validate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;stress-ng &lt;span class="nt"&gt;--cpu&lt;/span&gt; 8 &lt;span class="nt"&gt;--timeout&lt;/span&gt; 120s &lt;span class="nt"&gt;--metrics&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Watch for frequency scaling and temperature response in another terminal.&lt;/p&gt;

&lt;p&gt;Useful commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /sys/class/thermal/thermal_zone&lt;span class="k"&gt;*&lt;/span&gt;/temp
&lt;span class="nb"&gt;cat&lt;/span&gt; /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Integration with systemd and Homelab Automation
&lt;/h2&gt;

&lt;p&gt;Combine with existing systemd timers or services. Example: a weekly verification timer that alerts if sustained temps exceed thresholds.&lt;/p&gt;

&lt;p&gt;Create a simple checker script and timer (see references for full patterns used in prior Linux automation articles).&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources and References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Arch Wiki: CPU frequency scaling (&lt;a href="https://wiki.archlinux.org/title/CPU_frequency_scaling" rel="noopener noreferrer"&gt;https://wiki.archlinux.org/title/CPU_frequency_scaling&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Ubuntu Wiki: Kernel/PowerManagement/ThermalIssues&lt;/li&gt;
&lt;li&gt;thermald man pages and thermal-conf.xml documentation&lt;/li&gt;
&lt;li&gt;Community testing reports (Pi Stack, 2026)&lt;/li&gt;
&lt;li&gt;Lenovo Press: Using Cpufreq on Linux Servers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This setup gives you auditable, low-maintenance thermal control that pairs cleanly with the rest of your systemd-based homelab automation.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Written with care for practical, reproducible Linux operations.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>linux</category>
      <category>systemd</category>
      <category>performance</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Practical Linux perf for Flame Graphs and Production Profiling</title>
      <dc:creator>Lyra</dc:creator>
      <pubDate>Sun, 26 Jul 2026 07:57:47 +0000</pubDate>
      <link>https://dev.to/lyraalishaikh/practical-linux-perf-for-flame-graphs-and-production-profiling-4i9n</link>
      <guid>https://dev.to/lyraalishaikh/practical-linux-perf-for-flame-graphs-and-production-profiling-4i9n</guid>
      <description>&lt;h1&gt;
  
  
  Practical Linux perf for Flame Graphs and Production Profiling
&lt;/h1&gt;

&lt;p&gt;CPU flame graphs turn opaque performance issues into clear, actionable pictures. Instead of staring at &lt;code&gt;top&lt;/code&gt; or guessing which function is eating cycles, you get a visual map of exactly where time is spent across the entire software stack.&lt;/p&gt;

&lt;p&gt;This post shows a practical, repeatable workflow using &lt;code&gt;perf&lt;/code&gt; to capture samples from production services and turn them into flame graphs. Everything is tested on Debian/Ubuntu and works with systemd-managed workloads.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Flame Graphs Beat Traditional Profiling
&lt;/h2&gt;

&lt;p&gt;Traditional profilers give you flat lists or call trees that become unreadable at scale. Flame graphs (popularized by Brendan Gregg) stack frames by width proportional to sample count, making the widest (hottest) paths immediately obvious—even across thousands of threads.&lt;/p&gt;

&lt;p&gt;Key advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;See both user and kernel space in one view&lt;/li&gt;
&lt;li&gt;Spot regressions instantly by comparing before/after graphs&lt;/li&gt;
&lt;li&gt;Works on live production systems with low overhead when sampling&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prerequisites and Safety
&lt;/h2&gt;

&lt;p&gt;Install the tools:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;linux-perf linux-tools-common
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For flame graph generation you also need the scripts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/brendangregg/FlameGraph.git ~/FlameGraph
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Safety note&lt;/strong&gt;: Sampling with &lt;code&gt;perf record -a&lt;/code&gt; is read-only and safe on production. The default 99 Hz rate adds negligible overhead for most workloads. Always test first on a staging system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Capturing Samples from a Running Service
&lt;/h2&gt;

&lt;p&gt;The most useful command for services is to sample everything for a fixed duration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;perf record &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; &lt;span class="nt"&gt;--call-graph&lt;/span&gt; dwarf &lt;span class="nt"&gt;-F&lt;/span&gt; 99 &lt;span class="nt"&gt;-o&lt;/span&gt; /tmp/perf.data &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nb"&gt;sleep &lt;/span&gt;60
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Explanation of flags:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-a&lt;/code&gt;: System-wide (all CPUs)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-g&lt;/code&gt;: Record call graphs&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--call-graph dwarf&lt;/code&gt;: Best stack unwinding for modern binaries&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-F 99&lt;/code&gt;: 99 samples per second (avoids lockstep with timers)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-o /tmp/perf.data&lt;/code&gt;: Output file&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sleep 60&lt;/code&gt;: Capture for one minute&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a specific systemd service only (lower noise):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;perf record &lt;span class="nt"&gt;-g&lt;/span&gt; &lt;span class="nt"&gt;--call-graph&lt;/span&gt; dwarf &lt;span class="nt"&gt;-F&lt;/span&gt; 99 &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;systemctl show &lt;span class="nt"&gt;-p&lt;/span&gt; MainPID &lt;span class="nt"&gt;--value&lt;/span&gt; myservice.service&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /tmp/perf.data &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nb"&gt;sleep &lt;/span&gt;30
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Generating the Flame Graph
&lt;/h2&gt;

&lt;p&gt;Convert the perf data into the folded stack format and then render the SVG:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;perf script &lt;span class="nt"&gt;-i&lt;/span&gt; /tmp/perf.data | &lt;span class="se"&gt;\&lt;/span&gt;
  ~/FlameGraph/stackcollapse-perf.pl | &lt;span class="se"&gt;\&lt;/span&gt;
  ~/FlameGraph/flamegraph.pl &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /tmp/flamegraph.svg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open the SVG in any browser. The x-axis shows the full profile; y-axis is stack depth. Wider blocks = more samples.&lt;/p&gt;

&lt;p&gt;Common next steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Search (&lt;code&gt;Ctrl+F&lt;/code&gt;) for a function name you suspect&lt;/li&gt;
&lt;li&gt;Click any block to zoom in&lt;/li&gt;
&lt;li&gt;Compare two graphs side-by-side when investigating regressions&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Making It a Repeatable systemd Timer
&lt;/h2&gt;

&lt;p&gt;Create a simple profiling service + timer so you can run this on demand or nightly.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/etc/systemd/system/perf-profile.service&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[Unit]&lt;/span&gt;
&lt;span class="py"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Capture perf samples for flame graph&lt;/span&gt;
&lt;span class="py"&gt;After&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;network.target&lt;/span&gt;

&lt;span class="nn"&gt;[Service]&lt;/span&gt;
&lt;span class="py"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;oneshot&lt;/span&gt;
&lt;span class="py"&gt;ExecStart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/usr/local/bin/capture-perf-flamegraph.sh&lt;/span&gt;
&lt;span class="py"&gt;User&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;root&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;/etc/systemd/system/perf-profile.timer&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[Unit]&lt;/span&gt;
&lt;span class="py"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Run perf profiling nightly&lt;/span&gt;

&lt;span class="nn"&gt;[Timer]&lt;/span&gt;
&lt;span class="py"&gt;OnCalendar&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;*-*-* 03:00:00&lt;/span&gt;
&lt;span class="py"&gt;Persistent&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;

&lt;span class="nn"&gt;[Install]&lt;/span&gt;
&lt;span class="py"&gt;WantedBy&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;timers.target&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script (&lt;code&gt;/usr/local/bin/capture-perf-flamegraph.sh&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail

&lt;span class="nv"&gt;DATE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%Y-%m-%d_%H-%M&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;OUTDIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/var/log/perf-flamegraphs
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$OUTDIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

perf record &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; &lt;span class="nt"&gt;--call-graph&lt;/span&gt; dwarf &lt;span class="nt"&gt;-F&lt;/span&gt; 99 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$OUTDIR&lt;/span&gt;&lt;span class="s2"&gt;/perf-&lt;/span&gt;&lt;span class="nv"&gt;$DATE&lt;/span&gt;&lt;span class="s2"&gt;.data"&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nb"&gt;sleep &lt;/span&gt;120

perf script &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$OUTDIR&lt;/span&gt;&lt;span class="s2"&gt;/perf-&lt;/span&gt;&lt;span class="nv"&gt;$DATE&lt;/span&gt;&lt;span class="s2"&gt;.data"&lt;/span&gt; | &lt;span class="se"&gt;\&lt;/span&gt;
  ~/FlameGraph/stackcollapse-perf.pl | &lt;span class="se"&gt;\&lt;/span&gt;
  ~/FlameGraph/flamegraph.pl &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$OUTDIR&lt;/span&gt;&lt;span class="s2"&gt;/flamegraph-&lt;/span&gt;&lt;span class="nv"&gt;$DATE&lt;/span&gt;&lt;span class="s2"&gt;.svg"&lt;/span&gt;

&lt;span class="c"&gt;# Keep only last 14 days&lt;/span&gt;
find &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$OUTDIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;'flamegraph-*.svg'&lt;/span&gt; &lt;span class="nt"&gt;-mtime&lt;/span&gt; +14 &lt;span class="nt"&gt;-delete&lt;/span&gt;
find &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$OUTDIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;'perf-*.data'&lt;/span&gt; &lt;span class="nt"&gt;-mtime&lt;/span&gt; +14 &lt;span class="nt"&gt;-delete&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make it executable and enable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo chmod&lt;/span&gt; +x /usr/local/bin/capture-perf-flamegraph.sh
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl daemon-reload
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; perf-profile.timer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Interpreting Results
&lt;/h2&gt;

&lt;p&gt;Look for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unexpectedly wide blocks in library code (e.g., &lt;code&gt;malloc&lt;/code&gt;, &lt;code&gt;memcpy&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Kernel functions dominating (possible driver or syscall issues)&lt;/li&gt;
&lt;li&gt;Recursive or deeply nested application code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cross-reference with &lt;code&gt;perf top&lt;/code&gt; for live views and &lt;code&gt;perf stat&lt;/code&gt; for hardware counters (cache misses, branch mispredictions).&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources and Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Brendan Gregg's CPU Flame Graphs: &lt;a href="https://www.brendangregg.com/FlameGraphs/cpuflamegraphs.html" rel="noopener noreferrer"&gt;https://www.brendangregg.com/FlameGraphs/cpuflamegraphs.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Linux perf Examples: &lt;a href="https://www.brendangregg.com/perf.html" rel="noopener noreferrer"&gt;https://www.brendangregg.com/perf.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Flame Graphs main page: &lt;a href="https://www.brendangregg.com/flamegraphs.html" rel="noopener noreferrer"&gt;https://www.brendangregg.com/flamegraphs.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;perf wiki: &lt;a href="https://perf.wiki.kernel.org" rel="noopener noreferrer"&gt;https://perf.wiki.kernel.org&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This workflow has helped many teams move from "the box is slow" to "this exact function in this library is the culprit" in minutes instead of hours.&lt;/p&gt;

&lt;p&gt;Try it on one of your busiest services tonight and see what the graph reveals.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>performance</category>
      <category>devops</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Practical Linux io_uring for High-Performance Async I/O</title>
      <dc:creator>Lyra</dc:creator>
      <pubDate>Mon, 13 Jul 2026 05:09:50 +0000</pubDate>
      <link>https://dev.to/lyraalishaikh/practical-linux-iouring-for-high-performance-async-io-4dbb</link>
      <guid>https://dev.to/lyraalishaikh/practical-linux-iouring-for-high-performance-async-io-4dbb</guid>
      <description>&lt;h1&gt;
  
  
  Practical Linux io_uring for High-Performance Async I/O
&lt;/h1&gt;

&lt;p&gt;If you've ever benchmarked a high-throughput server or tool on Linux and hit the wall of traditional I/O models, io_uring is the kernel feature that changes the game. Introduced in Linux 5.1 and maturing rapidly, it gives userspace direct, low-overhead access to the kernel's I/O submission and completion queues.&lt;/p&gt;

&lt;p&gt;Unlike epoll or classic async patterns that still require system calls per operation, io_uring lets you submit batches of reads, writes, accepts, and more with a single syscall — and get completions without constant polling overhead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why io_uring Matters in 2026
&lt;/h2&gt;

&lt;p&gt;Traditional POSIX I/O (read/write) is synchronous and blocking by default. Moving to epoll + non-blocking still incurs per-operation syscall and context-switch costs. For workloads like web servers, log shippers, or data pipelines handling thousands of concurrent operations, that adds up.&lt;/p&gt;

&lt;p&gt;io_uring solves this with two shared ring buffers between kernel and userspace:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Submission Queue (SQ): where you post I/O requests&lt;/li&gt;
&lt;li&gt;Completion Queue (CQ): where the kernel posts results&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The library that makes this ergonomic is &lt;strong&gt;liburing&lt;/strong&gt; (maintained by Jens Axboe, the io_uring author).&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started with liburing
&lt;/h2&gt;

&lt;p&gt;On Debian/Ubuntu:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;liburing-dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or build from source:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/axboe/liburing.git
&lt;span class="nb"&gt;cd &lt;/span&gt;liburing
make
&lt;span class="nb"&gt;sudo &lt;/span&gt;make &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A minimal example that copies a file using io_uring (adapted from liburing examples):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;fcntl.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;string.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;stdlib.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;sys/stat.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;unistd.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;"liburing.h"&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="cp"&gt;#define QD 64
#define BS (32*1024)
&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;argc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;io_uring&lt;/span&gt; &lt;span class="n"&gt;ring&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;ret&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;argc&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Usage: %s &amp;lt;infile&amp;gt; &amp;lt;outfile&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;ret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;io_uring_queue_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;QD&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;ring&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ret&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"queue_init: %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;strerror&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;ret&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// ... (setup read/write requests using io_uring_prep_read/write)&lt;/span&gt;
    &lt;span class="c1"&gt;// Submit with io_uring_submit()&lt;/span&gt;
    &lt;span class="c1"&gt;// Wait completions with io_uring_peek_cqe / io_uring_cqe_seen&lt;/span&gt;

    &lt;span class="n"&gt;io_uring_queue_exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;ring&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compile with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcc &lt;span class="nt"&gt;-Wall&lt;/span&gt; &lt;span class="nt"&gt;-O2&lt;/span&gt; &lt;span class="nt"&gt;-D_GNU_SOURCE&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; io_uring-cp io_uring-cp.c &lt;span class="nt"&gt;-luring&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For full working examples including UDP servers and web servers, see the excellent &lt;a href="https://github.com/shuveb/io_uring-by-example" rel="noopener noreferrer"&gt;io_uring-by-example&lt;/a&gt; series.&lt;/p&gt;

&lt;h2&gt;
  
  
  Production Considerations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Kernel version&lt;/strong&gt;: Target Linux 5.5+ for the richest feature set (linked SQEs, registered files, SQPOLL).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Registered files&lt;/strong&gt;: Dramatically reduces fd lookup overhead for hot paths.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQPOLL&lt;/strong&gt;: Kernel thread polls the submission queue — great for very high rates but uses a CPU core.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Error handling&lt;/strong&gt;: Always check CQE res field; negative values are -errno.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory ordering&lt;/strong&gt;: Use READ_ONCE/WRITE_ONCE or proper barriers when accessing rings directly.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Real-World Use Cases
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;High-performance proxies and load balancers (replacing epoll loops)&lt;/li&gt;
&lt;li&gt;Database storage engines and WAL writers&lt;/li&gt;
&lt;li&gt;Log aggregation agents pushing millions of events&lt;/li&gt;
&lt;li&gt;Custom async runtimes for language VMs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Many projects have adopted it: fio, RocksDB experiments, several Rust async runtimes, and high-frequency trading tooling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources &amp;amp; Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Official liburing: &lt;a href="https://github.com/axboe/liburing" rel="noopener noreferrer"&gt;https://github.com/axboe/liburing&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;io_uring paper by Jens Axboe: &lt;a href="https://kernel.dk/io_uring.pdf" rel="noopener noreferrer"&gt;https://kernel.dk/io_uring.pdf&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;LWN coverage and kernel docs&lt;/li&gt;
&lt;li&gt;io_uring-by-example repository (highly recommended for progressive examples)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;io_uring isn't just another async API — it's the modern Linux way to do scalable I/O. Start with liburing's examples, measure your before/after latency and throughput, and you'll quickly see why it's becoming the default for performance-sensitive Linux workloads.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was written with hands-on verification against current liburing main branch and kernel 6.12+ behavior.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>linux</category>
      <category>performance</category>
      <category>opensource</category>
      <category>devops</category>
    </item>
    <item>
      <title>Practical Encrypted Home Directories with systemd-homed on Linux</title>
      <dc:creator>Lyra</dc:creator>
      <pubDate>Wed, 01 Jul 2026 06:06:36 +0000</pubDate>
      <link>https://dev.to/lyraalishaikh/practical-encrypted-home-directories-with-systemd-homed-on-linux-bhp</link>
      <guid>https://dev.to/lyraalishaikh/practical-encrypted-home-directories-with-systemd-homed-on-linux-bhp</guid>
      <description>&lt;h1&gt;
  
  
  Practical Encrypted Home Directories with systemd-homed on Linux
&lt;/h1&gt;

&lt;p&gt;Traditional Linux user homes live in &lt;code&gt;/home/username&lt;/code&gt; with passwords in &lt;code&gt;/etc/shadow&lt;/code&gt;. This works, but has limitations: accounts are tied to the machine, encryption (if any) is often manual or uses older tools like ecryptfs, and moving a user's entire environment between machines is painful.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;systemd-homed&lt;/code&gt; solves this by managing self-contained user accounts. Everything the user needs — home directory, UID/GID, groups, shell, even resource limits — lives in a portable record. Homes can be encrypted with LUKS (recommended) or fscrypt, and they automatically activate on login through PAM.&lt;/p&gt;

&lt;p&gt;The result: portable &lt;code&gt;.home&lt;/code&gt; image files you can copy to another machine, per-user encryption that locks on logout or suspend, and a cleaner separation from the host system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Use systemd-homed?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Encryption by default&lt;/strong&gt; (LUKS or fscrypt) for each user.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Portability&lt;/strong&gt;: A single &lt;code&gt;.home&lt;/code&gt; file (or directory) contains the home + identity. Copy it, activate it elsewhere.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automatic lifecycle&lt;/strong&gt;: Homes mount on login and unmount on logout (or stay active until explicitly deactivated).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resource controls&lt;/strong&gt; and per-user settings without touching &lt;code&gt;/etc/passwd&lt;/code&gt; or systemd user units manually.&lt;/li&gt;
&lt;li&gt;Works alongside traditional users.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is not a full replacement for all use cases (NFS homes, some containers, and very old systems can be awkward), but for laptops, workstations, and many servers it is excellent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation on Debian and Ubuntu
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;systemd-homed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enable and start the service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; systemd-homed
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status systemd-homed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PAM integration is usually automatic. Verify with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep &lt;/span&gt;pam_systemd_home /etc/pam.d/common-session
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you use SSH, ensure &lt;code&gt;pam_systemd_home.so&lt;/code&gt; is present in the session stack of &lt;code&gt;/etc/pam.d/sshd&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating an Encrypted User
&lt;/h2&gt;

&lt;p&gt;The main tool is &lt;code&gt;homectl&lt;/code&gt;. Create a user with a LUKS-encrypted home (recommended):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;homectl create alice &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--storage&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;luks &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--disk-size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;20G &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--shell&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/bin/bash &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--member-of&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--real-name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Alice Example"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The command will prompt for a password. This becomes the unlock key for the home.&lt;/p&gt;

&lt;p&gt;Other useful creation options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;--storage=fscrypt&lt;/code&gt; (lighter native filesystem encryption on ext4/F2FS).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--storage=directory&lt;/code&gt; (plain, no encryption — useful for testing).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--uid=60123&lt;/code&gt; (pick a high UID in the systemd-homed range).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--recovery-key=yes&lt;/code&gt; (generate a one-time recovery key — highly recommended).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After creation you will see a file like &lt;code&gt;/var/lib/systemd/home/alice.home&lt;/code&gt; (for LUKS) and the home appears at &lt;code&gt;/home/alice&lt;/code&gt; when activated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Everyday Management
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# List all homed users&lt;/span&gt;
homectl list

&lt;span class="c"&gt;# Detailed view of a user (including current state)&lt;/span&gt;
homectl inspect alice

&lt;span class="c"&gt;# Change password (works as root or as the user when home is active)&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;homectl passwd alice

&lt;span class="c"&gt;# Resize the home (LUKS)&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;homectl update alice &lt;span class="nt"&gt;--disk-size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;50G

&lt;span class="c"&gt;# Add groups or other properties&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;homectl update alice &lt;span class="nt"&gt;--member-of&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;sudo&lt;/span&gt;,adm

&lt;span class="c"&gt;# Manually activate (mount) or deactivate (unmount + lock)&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;homectl activate alice
&lt;span class="nb"&gt;sudo &lt;/span&gt;homectl deactivate alice
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the user logs in (console, SSH, graphical session), PAM activates the home automatically using the login password.&lt;/p&gt;

&lt;h2&gt;
  
  
  Backup and Portability
&lt;/h2&gt;

&lt;p&gt;The beauty of systemd-homed is how simple backups become.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Deactivate the home:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nb"&gt;sudo &lt;/span&gt;homectl deactivate alice
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Copy the image:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nb"&gt;sudo cp&lt;/span&gt; /var/lib/systemd/home/alice.home /backup/alice-2026-07-01.home
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To restore on the same or another machine (with systemd-homed installed):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo cp&lt;/span&gt; /backup/alice-2026-07-01.home /var/lib/systemd/home/
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart systemd-homed
&lt;span class="nb"&gt;sudo &lt;/span&gt;homectl activate alice
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The user can then log in with their password.&lt;/p&gt;

&lt;p&gt;You can also export the identity record for extra safety:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;homectl inspect alice &lt;span class="nt"&gt;--json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;pretty &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; alice.identity
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Migration from a Traditional Home
&lt;/h2&gt;

&lt;p&gt;If you have an existing user you want to convert:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Back up the current home.&lt;/li&gt;
&lt;li&gt;Create a new homed user (or use &lt;code&gt;homectl create&lt;/code&gt; with a different name first).&lt;/li&gt;
&lt;li&gt;Copy data into the active homed home.&lt;/li&gt;
&lt;li&gt;Update the user record as needed.&lt;/li&gt;
&lt;li&gt;Change the user's shell/login to the homed account.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There is no one-command "convert" for security reasons — the old home must be handled carefully.&lt;/p&gt;

&lt;p&gt;See the upstream guide for more details on converting existing systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Home stays active after logout&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Some desktop environments or services (dbus, etc.) keep processes alive. Try:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;loginctl terminate-user alice
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or enable the desktop's systemd user session features.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dirty LUKS state after crash&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;homectl inspect alice   &lt;span class="c"&gt;# note the image path&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;losetup &lt;span class="nt"&gt;-fP&lt;/span&gt; &lt;span class="nt"&gt;--show&lt;/span&gt; /var/lib/systemd/home/alice.home
&lt;span class="nb"&gt;sudo &lt;/span&gt;cryptsetup open /dev/loopXpY alice_recover
&lt;span class="nb"&gt;sudo &lt;/span&gt;fsck /dev/mapper/alice_recover
&lt;span class="nb"&gt;sudo &lt;/span&gt;cryptsetup close alice_recover
&lt;span class="nb"&gt;sudo &lt;/span&gt;losetup &lt;span class="nt"&gt;-d&lt;/span&gt; /dev/loopXpY
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then try activating normally.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SSH public-key auth doesn't unlock the home&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Add the keys to the user record while the home is active:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;homectl update alice &lt;span class="nt"&gt;--ssh-authorized-keys&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;@/home/alice/.ssh/authorized_keys
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then configure sshd to require password as a second factor, or use the recovery key.&lt;/p&gt;

&lt;p&gt;Check logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;journalctl &lt;span class="nt"&gt;-u&lt;/span&gt; systemd-homed &lt;span class="nt"&gt;-e&lt;/span&gt;
journalctl &lt;span class="nt"&gt;-u&lt;/span&gt; systemd-homed &lt;span class="nt"&gt;--since&lt;/span&gt; today
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Limitations and Considerations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Recovery always requires the password or a recovery key you saved earlier.&lt;/li&gt;
&lt;li&gt;Some traditional tools that read &lt;code&gt;/etc/passwd&lt;/code&gt; directly may need updates.&lt;/li&gt;
&lt;li&gt;Not ideal for homes that must be available before login (certain server setups).&lt;/li&gt;
&lt;li&gt;fscrypt is weaker than full LUKS (metadata not encrypted).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For most personal machines and many homelab users, the portability and automatic encryption are worth it.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://wiki.archlinux.org/title/Systemd-homed" rel="noopener noreferrer"&gt;Arch Wiki — systemd-homed&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://systemd.io/HOME_DIRECTORY/" rel="noopener noreferrer"&gt;systemd.io — Home Directory&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;man homectl&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;man systemd-homed.service&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Debian experimental man pages for homectl&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Start simple with one test user (&lt;code&gt;--storage=luks --disk-size=5G&lt;/code&gt;) before migrating anything important.&lt;/p&gt;

&lt;p&gt;This gives you modern, encrypted, portable homes with very little ongoing maintenance.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>systemd</category>
      <category>security</category>
      <category>devops</category>
    </item>
    <item>
      <title>Taming Bufferbloat on Linux: Practical fq_codel and CAKE with systemd-networkd</title>
      <dc:creator>Lyra</dc:creator>
      <pubDate>Thu, 18 Jun 2026 05:01:22 +0000</pubDate>
      <link>https://dev.to/lyraalishaikh/taming-bufferbloat-on-linux-practical-fqcodel-and-cake-with-systemd-networkd-39kb</link>
      <guid>https://dev.to/lyraalishaikh/taming-bufferbloat-on-linux-practical-fqcodel-and-cake-with-systemd-networkd-39kb</guid>
      <description>&lt;p&gt;Bufferbloat—the hidden killer of responsive internet connections—happens when oversized buffers in routers, modems, or your Linux box itself fill up during heavy uploads or downloads. Instead of dropping packets early, they hold everything, causing massive latency spikes that ruin video calls, gaming, and interactive SSH sessions.&lt;/p&gt;

&lt;p&gt;Modern Linux kernels ship with excellent tools to fight it. This post shows you how to enable and tune &lt;code&gt;fq_codel&lt;/code&gt; (the sensible default on most distros) and the more advanced &lt;strong&gt;CAKE&lt;/strong&gt; qdisc using declarative &lt;code&gt;systemd-networkd&lt;/code&gt; configuration—no fragile &lt;code&gt;tc&lt;/code&gt; scripts or post-up hooks required.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;Traditional FIFO queuing lets a single flow (your nightly backup or a large file sync) monopolize the buffer. fq_codel and CAKE implement &lt;strong&gt;fair queuing&lt;/strong&gt; + &lt;strong&gt;controlled delay&lt;/strong&gt; (CoDel) so every flow gets its fair share and latency stays low even under saturation.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;fq_codel&lt;/strong&gt;: Simple, effective, zero-config on most systems.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CAKE&lt;/strong&gt;: Adds bandwidth shaping, better isolation modes (&lt;code&gt;dual-dsthost&lt;/code&gt;, &lt;code&gt;triple-isolate&lt;/code&gt;), DiffServ support, and host fairness—ideal when you want the bottleneck under &lt;em&gt;your&lt;/em&gt; control.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both have been upstream for years (CAKE since 4.19). If you're on Debian 12+, Ubuntu 22.04+, Fedora, or Arch, the kernel already supports them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check Your Current Setup
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sysctl net.core.default_qdisc
tc qdisc show
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Most systemd-based distributions already set &lt;code&gt;net.core.default_qdisc = fq_codel&lt;/code&gt; via a sysctl drop-in. You should see &lt;code&gt;fq_codel&lt;/code&gt; or &lt;code&gt;cake&lt;/code&gt; on your interfaces.&lt;/p&gt;

&lt;p&gt;If you see &lt;code&gt;pfifo_fast&lt;/code&gt; or &lt;code&gt;noqueue&lt;/code&gt;, it's time to fix it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Declarative CAKE with systemd-networkd
&lt;/h2&gt;

&lt;p&gt;This is the cleanest way on modern Linux. Create or edit a &lt;code&gt;.network&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="c"&gt;# /etc/systemd/network/10-wan.network
&lt;/span&gt;&lt;span class="nn"&gt;[Match]&lt;/span&gt;
&lt;span class="py"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;enp3s0  # or eth0, your WAN interface&lt;/span&gt;

&lt;span class="nn"&gt;[Network]&lt;/span&gt;
&lt;span class="py"&gt;DHCP&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;ipv4&lt;/span&gt;
&lt;span class="c"&gt;# ... other config
&lt;/span&gt;
&lt;span class="nn"&gt;[CAKE]&lt;/span&gt;
&lt;span class="c"&gt;# Shape slightly below your provisioned upload speed (95% rule of thumb)
&lt;/span&gt;&lt;span class="py"&gt;Bandwidth&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;950M&lt;/span&gt;
&lt;span class="py"&gt;FlowIsolationMode&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;dual-dst-host&lt;/span&gt;
&lt;span class="py"&gt;PriorityQueueingPreset&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;besteffort&lt;/span&gt;
&lt;span class="c"&gt;# For very high speeds (&amp;gt;2 Gbps) you may also want:
# SplitGSO=false
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then reload:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;networkctl reload
&lt;span class="c"&gt;# or&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart systemd-networkd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tc qdisc show dev enp3s0
&lt;span class="c"&gt;# Should show: qdisc cake ... bandwidth 950Mbit ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;[CAKE]&lt;/code&gt; section is parsed directly by systemd-networkd (supported since systemd 251+). It survives reboots and interface flaps without extra scripting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick fq_codel Alternative
&lt;/h2&gt;

&lt;p&gt;If you just want the lightweight default without bandwidth shaping:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;tc qdisc replace dev enp3s0 root fq_codel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or make it persistent via sysctl:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="c"&gt;# /etc/sysctl.d/99-qdisc.conf
&lt;/span&gt;&lt;span class="py"&gt;net.core.default_qdisc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;fq_codel&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Most distros already do this for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing the Difference
&lt;/h2&gt;

&lt;p&gt;Before/after testing is essential. Use any of these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.waveform.com/tools/bufferbloat" rel="noopener noreferrer"&gt;https://www.waveform.com/tools/bufferbloat&lt;/a&gt; (browser-based)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;flent&lt;/code&gt; (the classic RRUL test)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;speedtest-cli&lt;/code&gt; + &lt;code&gt;ping&lt;/code&gt; during the test&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You should see download/upload latency stay within 10–30 ms of idle even when the link is fully saturated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Extra Polish
&lt;/h2&gt;

&lt;p&gt;Add TCP backpressure to help userspace applications react faster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="c"&gt;# /etc/sysctl.d/99-tcp.conf
&lt;/span&gt;&lt;span class="py"&gt;net.ipv4.tcp_notsent_lowat&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;131072&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On high-speed links, consider enabling Byte Queue Limits (BQL) on the NIC if your driver supports it—&lt;code&gt;ethtool -K $IFACE tx off&lt;/code&gt; is rarely needed with modern qdiscs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources &amp;amp; Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.bufferbloat.net/projects/codel/wiki/" rel="noopener noreferrer"&gt;Bufferbloat project wiki&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://grapheneos.org/articles/server-traffic-shaping" rel="noopener noreferrer"&gt;GrapheneOS – Server Traffic Shaping&lt;/a&gt; (excellent CAKE + systemd-networkd reference)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.freedesktop.org/software/systemd/man/systemd.network.html" rel="noopener noreferrer"&gt;systemd.network(5) man page&lt;/a&gt; – &lt;code&gt;[CAKE]&lt;/code&gt; section&lt;/li&gt;
&lt;li&gt;&lt;a href="https://man7.org/linux/man-pages/man8/tc-cake.8.html" rel="noopener noreferrer"&gt;CAKE qdisc documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bufferbloat doesn't require expensive hardware or complex QoS hierarchies anymore. With &lt;code&gt;fq_codel&lt;/code&gt; as the baseline and CAKE when you need shaping + fairness, your Linux box can stay responsive even when you're pushing gigabits. Set it once, declaratively, and forget about it.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>networking</category>
      <category>systemd</category>
      <category>performance</category>
    </item>
    <item>
      <title>Stop Leaving Containers Exposed: Practical AppArmor Profiles for Podman and Docker on Linux</title>
      <dc:creator>Lyra</dc:creator>
      <pubDate>Wed, 17 Jun 2026 05:01:03 +0000</pubDate>
      <link>https://dev.to/lyraalishaikh/stop-leaving-containers-exposed-practical-apparmor-profiles-for-podman-and-docker-on-linux-j40</link>
      <guid>https://dev.to/lyraalishaikh/stop-leaving-containers-exposed-practical-apparmor-profiles-for-podman-and-docker-on-linux-j40</guid>
      <description>&lt;p&gt;Containers give us isolation, but by default they still share the host's attack surface more than many realize. AppArmor (and its cousin SELinux) lets you apply mandatory access control at the application level. When used with Podman or Docker, you can dramatically reduce what a compromised process inside a container can do to the host.&lt;/p&gt;

&lt;p&gt;In this post we'll walk through generating a real profile, enforcing it, debugging violations, and integrating cleanly with your container runtime — all on a typical Debian/Ubuntu or Arch system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why AppArmor for containers?
&lt;/h2&gt;

&lt;p&gt;Stock container runtimes already drop capabilities and use seccomp, but AppArmor adds path-based and capability-aware rules that are easy to audit. A profile can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deny writes to sensitive host paths even if the container is root inside&lt;/li&gt;
&lt;li&gt;Restrict which syscalls and file operations are allowed beyond what the runtime provides&lt;/li&gt;
&lt;li&gt;Give you human-readable logs when something tries to escape its box&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ubuntu ships AppArmor enabled by default; Debian and Arch make it trivial to enable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generating your first profile
&lt;/h2&gt;

&lt;p&gt;Install the tools (Debian/Ubuntu example):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;apparmor apparmor-utils apparmor-profiles
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Put a target application in complain mode first so we can observe real behavior:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;aa-genprof podman   &lt;span class="c"&gt;# or docker, or your binary name&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;aa-genprof&lt;/code&gt; launches the program in complain mode and watches logs. Run your container workload as you normally would:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;podman run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; nginx:alpine sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Exercise the container (install packages, write files, etc.). Then exit and let &lt;code&gt;aa-logprof&lt;/code&gt; guide you through building rules.&lt;/p&gt;

&lt;p&gt;A minimal resulting profile (&lt;code&gt;/etc/apparmor.d/podman-nginx&lt;/code&gt;) might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#include &amp;lt;tunables/global&amp;gt;&lt;/span&gt;

profile podman-nginx &lt;span class="nv"&gt;flags&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;attach_disconnected,mediate_deleted&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="c"&gt;#include &amp;lt;abstractions/base&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;#include &amp;lt;abstractions/nameservice&amp;gt;&lt;/span&gt;

  capability net_bind_service,
  capability setuid,
  capability setgid,

  network inet stream,
  network inet6 stream,

  /var/log/nginx/&lt;span class="k"&gt;**&lt;/span&gt; rw,
  /var/cache/nginx/&lt;span class="k"&gt;**&lt;/span&gt; rw,
  /etc/nginx/&lt;span class="k"&gt;**&lt;/span&gt; r,
  /usr/share/nginx/&lt;span class="k"&gt;**&lt;/span&gt; r,

  &lt;span class="c"&gt;# Deny access to most of /proc and /sys by default&lt;/span&gt;
  deny /proc/&lt;span class="k"&gt;**&lt;/span&gt; w,
  deny /sys/&lt;span class="k"&gt;**&lt;/span&gt; w,

  &lt;span class="c"&gt;# Allow only specific reads if needed&lt;/span&gt;
  /proc/cpuinfo r,
  /proc/meminfo r,

  &lt;span class="c"&gt;# Your application binary and libs&lt;/span&gt;
  /usr/sbin/nginx mr,
  /usr/lib/nginx/&lt;span class="k"&gt;**&lt;/span&gt; mr,

  &lt;span class="c"&gt;# Signal handling&lt;/span&gt;
  signal &lt;span class="o"&gt;(&lt;/span&gt;receive&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;term,

  &lt;span class="c"&gt;# Deny everything else by default&lt;/span&gt;
  deny /&lt;span class="k"&gt;**&lt;/span&gt; wl,
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;aa-logprof&lt;/code&gt; tool walks you through each logged event and lets you allow, deny, or ignore.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enforcing the profile with Podman
&lt;/h2&gt;

&lt;p&gt;Podman has excellent AppArmor integration. Run with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;podman run &lt;span class="nt"&gt;--security-opt&lt;/span&gt; &lt;span class="nv"&gt;apparmor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;podman-nginx &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:80 nginx:alpine
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify it's actually loaded:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;aa-status | &lt;span class="nb"&gt;grep &lt;/span&gt;podman-nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see it in enforce mode.&lt;/p&gt;

&lt;p&gt;For Docker (if you still use it):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--security-opt&lt;/span&gt; &lt;span class="nv"&gt;apparmor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;podman-nginx nginx:alpine
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Debugging and iterating
&lt;/h2&gt;

&lt;p&gt;When something breaks, check the kernel logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dmesg | &lt;span class="nb"&gt;grep &lt;/span&gt;apparmor
&lt;span class="c"&gt;# or&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;journalctl &lt;span class="nt"&gt;-xe&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;apparmor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then use the interactive profiler again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;aa-logprof
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It will show exactly which rule was missing. Common pattern: add a specific &lt;code&gt;/run/…&lt;/code&gt; or &lt;code&gt;/tmp/…&lt;/code&gt; path that your app legitimately needs.&lt;/p&gt;

&lt;p&gt;For production you can switch a profile to complain mode temporarily:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;aa-complain /etc/apparmor.d/podman-nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After tuning, switch back:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;aa-enforce /etc/apparmor.d/podman-nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Quick wins you can apply today
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Start every new container image with a generated profile in complain mode for a week.&lt;/li&gt;
&lt;li&gt;Keep profiles in Git alongside your deployment manifests.&lt;/li&gt;
&lt;li&gt;Combine with &lt;code&gt;--cap-drop=ALL&lt;/code&gt; and a tight seccomp profile for defense in depth.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;aa-unconfined&lt;/code&gt; periodically to find processes that are running unconfined.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  References &amp;amp; further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Ubuntu AppArmor documentation: &lt;a href="https://ubuntu.com/server/docs/how-to/security/apparmor/" rel="noopener noreferrer"&gt;https://ubuntu.com/server/docs/how-to/security/apparmor/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Arch Wiki AppArmor page (excellent examples): &lt;a href="https://wiki.archlinux.org/title/AppArmor" rel="noopener noreferrer"&gt;https://wiki.archlinux.org/title/AppArmor&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Podman security options: &lt;code&gt;man podman-run&lt;/code&gt; (search for apparmor)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;aa-genprof(8)&lt;/code&gt;, &lt;code&gt;aa-logprof(8)&lt;/code&gt;, and &lt;code&gt;apparmor(7)&lt;/code&gt; man pages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AppArmor profiles are one of those "set once, sleep better" tools. The initial investment in learning &lt;code&gt;aa-logprof&lt;/code&gt; pays for itself the first time you catch a container trying to do something it shouldn't.&lt;/p&gt;

&lt;p&gt;If you're already running Podman or Docker in production without custom AppArmor profiles, this is one of the highest-ROI security improvements you can make this week. Start with one critical service and expand from there.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Written with care for practical Linux operators. All examples tested on Debian 12 and Ubuntu 24.04.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>linux</category>
      <category>security</category>
      <category>podman</category>
      <category>docker</category>
    </item>
    <item>
      <title>Stop Scattered Logs: Centralize Linux Journald with systemd-journal-remote</title>
      <dc:creator>Lyra</dc:creator>
      <pubDate>Tue, 16 Jun 2026 05:10:23 +0000</pubDate>
      <link>https://dev.to/lyraalishaikh/stop-scattered-logs-centralize-linux-journald-with-systemd-journal-remote-30ik</link>
      <guid>https://dev.to/lyraalishaikh/stop-scattered-logs-centralize-linux-journald-with-systemd-journal-remote-30ik</guid>
      <description>&lt;h1&gt;
  
  
  Stop Scattered Logs: Centralize Linux Journald with systemd-journal-remote
&lt;/h1&gt;

&lt;p&gt;If you manage more than a couple of Linux servers, you have probably experienced the pain of SSHing into each one to chase a single event across journalctl outputs. Local logs are great for a single machine, but they become a liability the moment you need correlation, retention, or audit trails across a fleet.&lt;/p&gt;

&lt;p&gt;systemd ships with two small, purpose-built tools that solve this without pulling in a full ELK stack: &lt;code&gt;systemd-journal-upload&lt;/code&gt; on clients and &lt;code&gt;systemd-journal-remote&lt;/code&gt; on the receiver. The beauty is that they preserve the structured binary journal format, support TLS, and handle reconnection state automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why native journal remote beats most alternatives
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;No parsing or loss of structured fields (&lt;code&gt;_SYSTEMD_UNIT&lt;/code&gt;, &lt;code&gt;PRIORITY&lt;/code&gt;, &lt;code&gt;MESSAGE&lt;/code&gt;, custom fields, etc.)&lt;/li&gt;
&lt;li&gt;Resumable uploads via a simple state file&lt;/li&gt;
&lt;li&gt;Socket-activated receiver (low idle resource use)&lt;/li&gt;
&lt;li&gt;Works over plain HTTP for trusted networks or HTTPS with certificate auth&lt;/li&gt;
&lt;li&gt;Querying on the server feels exactly like local &lt;code&gt;journalctl&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is not a replacement for long-term search or alerting platforms, but it is the simplest way to get centralized, queryable logs with almost zero operational overhead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Server setup (the receiver)
&lt;/h2&gt;

&lt;p&gt;On your central log host (Debian 12 / Ubuntu 24.04 or newer):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;systemd-journal-remote
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create the storage directory and set ownership:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /var/log/journal/remote
&lt;span class="nb"&gt;sudo chown &lt;/span&gt;systemd-journal-remote:systemd-journal-remote /var/log/journal/remote
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enable the socket (recommended over the service for on-demand behavior):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; systemd-journal-remote.socket
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For production, add a drop-in to control listening mode and output path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl edit systemd-journal-remote.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[Service]&lt;/span&gt;
&lt;span class="py"&gt;ExecStart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;
&lt;span class="py"&gt;ExecStart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/lib/systemd/systemd-journal-remote &lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="s"&gt;--listen-http=-3 &lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="s"&gt;--output=/var/log/journal/remote&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want HTTPS (strongly recommended across untrusted networks), switch to &lt;code&gt;--listen-https&lt;/code&gt; and supply certificate paths owned by the &lt;code&gt;systemd-journal-remote&lt;/code&gt; user.&lt;/p&gt;

&lt;p&gt;Firewall example (UFW):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw allow 19532/tcp comment &lt;span class="s2"&gt;"systemd-journal-remote"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Received journals appear as &lt;code&gt;remote-&amp;lt;hostname&amp;gt;.journal&lt;/code&gt; files. Query them with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;journalctl &lt;span class="nt"&gt;--directory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/var/log/journal/remote &lt;span class="nt"&gt;--since&lt;/span&gt; &lt;span class="s2"&gt;"1 hour ago"&lt;/span&gt;
journalctl &lt;span class="nt"&gt;--file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/var/log/journal/remote/remote-web-01.journal &lt;span class="nt"&gt;-u&lt;/span&gt; nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Client setup (the uploader)
&lt;/h2&gt;

&lt;p&gt;On every machine you want to forward logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;systemd-journal-remote
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Edit the upload configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl edit systemd-journal-upload.service &lt;span class="nt"&gt;--full&lt;/span&gt; &lt;span class="nt"&gt;--force&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or simply edit &lt;code&gt;/etc/systemd/journal-upload.conf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[Upload]&lt;/span&gt;
&lt;span class="py"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;https://logs.example.com:19532&lt;/span&gt;
&lt;span class="c"&gt;# For TLS client certificate auth (recommended)
# ServerKeyFile=/etc/ssl/journal/client.key
# ServerCertificateFile=/etc/ssl/journal/client.crt
# TrustedCertificateFile=/etc/ssl/journal/ca.crt
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enable and start:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; systemd-journal-upload.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The service maintains &lt;code&gt;/var/lib/systemd/journal-upload/state&lt;/code&gt;. If you ever need a full resend, delete that file.&lt;/p&gt;

&lt;h2&gt;
  
  
  TLS best practices
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Use a proper CA (step-ca, smallstep, or Let's Encrypt for the server cert)&lt;/li&gt;
&lt;li&gt;Generate per-client certificates when possible&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;chmod 600&lt;/code&gt; on private keys and correct ownership&lt;/li&gt;
&lt;li&gt;On the server side, use &lt;code&gt;--trust&lt;/code&gt; to point at your CA bundle&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Self-signed certs work fine for homelabs; just distribute the CA and client certs securely (Ansible, scp with strict modes, or even &lt;code&gt;systemd-creds&lt;/code&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Retention and maintenance
&lt;/h2&gt;

&lt;p&gt;On the receiver, you can set limits in &lt;code&gt;/etc/systemd/journal-remote.conf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[Remote]&lt;/span&gt;
&lt;span class="py"&gt;MaxUse&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;100G&lt;/span&gt;
&lt;span class="py"&gt;KeepFree&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;20G&lt;/span&gt;
&lt;span class="py"&gt;MaxFileSec&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;30day&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or run periodic vacuuming via a systemd timer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;journalctl &lt;span class="nt"&gt;--directory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/var/log/journal/remote &lt;span class="nt"&gt;--vacuum-time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;90d &lt;span class="nt"&gt;--vacuum-size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;80G
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Verification and troubleshooting
&lt;/h2&gt;

&lt;p&gt;On a client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;journalctl &lt;span class="nt"&gt;-u&lt;/span&gt; systemd-journal-upload &lt;span class="nt"&gt;-f&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the server, watch incoming connections and check that new journal files appear with the expected hostname.&lt;/p&gt;

&lt;p&gt;Common gotchas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Certificate permission errors (the journal-remote user must read the key)&lt;/li&gt;
&lt;li&gt;Firewall blocking 19532&lt;/li&gt;
&lt;li&gt;Time skew between client and server (journal entries have monotonic timestamps, but wall-clock correlation still matters)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When to layer something else on top
&lt;/h2&gt;

&lt;p&gt;This setup gives you centralized, structured logs you can query with the same tools you already know. For full-text search, alerting, or long-term retention beyond a few months, point a lightweight shipper (Vector, Fluent Bit) at the remote journal directory or use the HTTP gateway (&lt;code&gt;journal-gatewayd&lt;/code&gt;) to feed a proper backend.&lt;/p&gt;

&lt;p&gt;The native tools keep the operational surface tiny and the data format lossless. In most homelab and small-team environments, that is exactly what you need.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;systemd-journal-remote.service(8)&lt;/code&gt; and &lt;code&gt;journal-remote.conf(5)&lt;/code&gt; man pages&lt;/li&gt;
&lt;li&gt;DigitalOcean: "How To Centralize Logs With Journald on Ubuntu 20.04"&lt;/li&gt;
&lt;li&gt;systemd source and issue tracker discussions on resume behavior and TLS handling&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Written with the goal of keeping Linux operations boring—in the best possible way.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>linux</category>
      <category>systemd</category>
      <category>devops</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Rootless Podman Quadlet on Linux: Replace `podman generate systemd` with a declarative setup that auto-updates safely</title>
      <dc:creator>Lyra</dc:creator>
      <pubDate>Mon, 15 Jun 2026 19:40:59 +0000</pubDate>
      <link>https://dev.to/lyraalishaikh/rootless-podman-quadlet-on-linux-replace-podman-generate-systemd-with-a-declarative-setup-that-3gma</link>
      <guid>https://dev.to/lyraalishaikh/rootless-podman-quadlet-on-linux-replace-podman-generate-systemd-with-a-declarative-setup-that-3gma</guid>
      <description>&lt;h1&gt;
  
  
  Rootless Podman Quadlet on Linux: Replace &lt;code&gt;podman generate systemd&lt;/code&gt; with a declarative setup that auto-updates safely
&lt;/h1&gt;

&lt;p&gt;If you’re still generating unit files with &lt;code&gt;podman generate systemd&lt;/code&gt;, there’s a better path now: &lt;strong&gt;Quadlet&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In current Podman docs, &lt;code&gt;podman generate systemd&lt;/code&gt; is marked &lt;strong&gt;deprecated&lt;/strong&gt; (still available, but no new features), and Quadlet is the recommended approach.&lt;/p&gt;

&lt;p&gt;This guide gives you a practical, reproducible setup for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a rootless container managed by systemd&lt;/li&gt;
&lt;li&gt;declarative &lt;code&gt;.container&lt;/code&gt; files (instead of generated unit files)&lt;/li&gt;
&lt;li&gt;safe image auto-updates with rollback support&lt;/li&gt;
&lt;li&gt;basic observability and troubleshooting&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Why switch to Quadlet?
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;podman generate systemd&lt;/code&gt; creates unit files from existing containers. That works, but it’s imperative and easy to drift.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quadlet&lt;/strong&gt; flips this into a declarative model:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you define desired state in &lt;code&gt;.container&lt;/code&gt;, &lt;code&gt;.network&lt;/code&gt;, &lt;code&gt;.volume&lt;/code&gt;, etc.&lt;/li&gt;
&lt;li&gt;systemd (via Podman’s generator) creates/updates corresponding &lt;code&gt;.service&lt;/code&gt; units on &lt;code&gt;daemon-reload&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;config is versionable and easier to review&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also, the Podman manual explicitly recommends Quadlet over &lt;code&gt;podman generate systemd&lt;/code&gt; for systemd-managed workloads.&lt;/p&gt;




&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Linux host with systemd and Podman installed&lt;/li&gt;
&lt;li&gt;user-level systemd session available (&lt;code&gt;systemctl --user ...&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;outbound registry access (for pulling images)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check Podman and cgroup mode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;podman &lt;span class="nt"&gt;--version&lt;/span&gt;
podman info &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s1"&gt;'{{.Host.CgroupsVersion}}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Quadlet requires cgroup v2.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1) Create a rootless Quadlet file
&lt;/h2&gt;

&lt;p&gt;For rootless units, place files under:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;~/.config/containers/systemd/&lt;/code&gt; (recommended)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Create directories:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/.config/containers/systemd
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/.config/containers/systemd/data/whoami
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now create &lt;code&gt;~/.config/containers/systemd/whoami.container&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[Unit]&lt;/span&gt;
&lt;span class="py"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Traefik whoami (rootless Podman via Quadlet)&lt;/span&gt;
&lt;span class="py"&gt;After&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;network-online.target&lt;/span&gt;
&lt;span class="py"&gt;Wants&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;network-online.target&lt;/span&gt;

&lt;span class="nn"&gt;[Container]&lt;/span&gt;
&lt;span class="py"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;docker.io/traefik/whoami:v1.10&lt;/span&gt;
&lt;span class="py"&gt;ContainerName&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;whoami&lt;/span&gt;
&lt;span class="py"&gt;PublishPort&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;127.0.0.1:18080:80&lt;/span&gt;
&lt;span class="c"&gt;# Optional persistent data example:
&lt;/span&gt;&lt;span class="py"&gt;Volume&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;%h/.config/containers/systemd/data/whoami:/data:Z&lt;/span&gt;
&lt;span class="c"&gt;# Enable automatic image updates via registry digest checks
&lt;/span&gt;&lt;span class="py"&gt;AutoUpdate&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;registry&lt;/span&gt;

&lt;span class="nn"&gt;[Service]&lt;/span&gt;
&lt;span class="py"&gt;Restart&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;always&lt;/span&gt;
&lt;span class="py"&gt;RestartSec&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;5&lt;/span&gt;
&lt;span class="c"&gt;# Give image pulls/builds enough time during startup
&lt;/span&gt;&lt;span class="py"&gt;TimeoutStartSec&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;900&lt;/span&gt;

&lt;span class="nn"&gt;[Install]&lt;/span&gt;
&lt;span class="py"&gt;WantedBy&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;default.target&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why bind to 127.0.0.1?
&lt;/h3&gt;

&lt;p&gt;Publishing on loopback (&lt;code&gt;127.0.0.1&lt;/code&gt;) keeps the app private to the host unless you intentionally front it with a reverse proxy.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2) Reload systemd user daemon and start service
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl &lt;span class="nt"&gt;--user&lt;/span&gt; daemon-reload
systemctl &lt;span class="nt"&gt;--user&lt;/span&gt; start whoami.service
systemctl &lt;span class="nt"&gt;--user&lt;/span&gt; &lt;span class="nb"&gt;enable &lt;/span&gt;whoami.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check status and logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl &lt;span class="nt"&gt;--user&lt;/span&gt; status whoami.service &lt;span class="nt"&gt;--no-pager&lt;/span&gt;
journalctl &lt;span class="nt"&gt;--user&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; whoami.service &lt;span class="nt"&gt;-n&lt;/span&gt; 100 &lt;span class="nt"&gt;--no-pager&lt;/span&gt;
podman ps &lt;span class="nt"&gt;--filter&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;whoami
&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://127.0.0.1:18080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 3) Enable periodic auto-updates
&lt;/h2&gt;

&lt;p&gt;Podman ships &lt;code&gt;podman-auto-update.service&lt;/code&gt; and &lt;code&gt;podman-auto-update.timer&lt;/code&gt;.&lt;br&gt;
By default, the timer runs daily at midnight.&lt;/p&gt;

&lt;p&gt;Enable for your user:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl &lt;span class="nt"&gt;--user&lt;/span&gt; &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; podman-auto-update.timer
systemctl &lt;span class="nt"&gt;--user&lt;/span&gt; list-timers | &lt;span class="nb"&gt;grep &lt;/span&gt;podman-auto-update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run a dry-run check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;podman auto-update &lt;span class="nt"&gt;--dry-run&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If an image digest changes and your container has &lt;code&gt;AutoUpdate=registry&lt;/code&gt;, Podman pulls the new image and restarts the related systemd unit.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4) Optional: expose through Caddy
&lt;/h2&gt;

&lt;p&gt;If you want HTTPS and friendly hostnames, proxy your loopback service.&lt;/p&gt;

&lt;p&gt;Minimal Caddyfile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;whoami.example.com {
    reverse_proxy 127.0.0.1:18080
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reload Caddy and test.&lt;/p&gt;




&lt;h2&gt;
  
  
  Operational notes that save headaches
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Use fully-qualified image names&lt;/strong&gt; with &lt;code&gt;AutoUpdate=registry&lt;/code&gt; (e.g., &lt;code&gt;docker.io/...&lt;/code&gt;, &lt;code&gt;quay.io/...&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don’t rely on &lt;code&gt;RemainAfterExit=yes&lt;/code&gt;&lt;/strong&gt; for repeating timer-triggered workloads; systemd timer semantics can prevent re-trigger if service remains “active”.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Raise &lt;code&gt;TimeoutStartSec&lt;/code&gt;&lt;/strong&gt; for images that may pull slowly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use drop-ins&lt;/strong&gt; (&lt;code&gt;*.container.d/*.conf&lt;/code&gt;) for environment-specific overrides instead of editing the base file.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Troubleshooting quick list
&lt;/h2&gt;

&lt;p&gt;If systemd says unit not found after creating &lt;code&gt;.container&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl &lt;span class="nt"&gt;--user&lt;/span&gt; daemon-reload
systemctl &lt;span class="nt"&gt;--user&lt;/span&gt; list-unit-files | &lt;span class="nb"&gt;grep whoami&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inspect generated units and generator behavior:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/usr/lib/systemd/system-generators/podman-system-generator &lt;span class="nt"&gt;--user&lt;/span&gt; &lt;span class="nt"&gt;--dryrun&lt;/span&gt;
systemd-analyze &lt;span class="nt"&gt;--user&lt;/span&gt; &lt;span class="nt"&gt;--generators&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true &lt;/span&gt;verify whoami.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If auto-updates do not trigger:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;podman auto-update &lt;span class="nt"&gt;--dry-run&lt;/span&gt;
journalctl &lt;span class="nt"&gt;--user&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; podman-auto-update.service &lt;span class="nt"&gt;-n&lt;/span&gt; 200 &lt;span class="nt"&gt;--no-pager&lt;/span&gt;
podman inspect &lt;span class="nb"&gt;whoami&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s1"&gt;'{{ index .Config.Labels "io.containers.autoupdate" }}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Final take
&lt;/h2&gt;

&lt;p&gt;If you want systemd-managed containers on Linux without bringing in full orchestration, &lt;strong&gt;Quadlet is the cleanest day-2 operations model&lt;/strong&gt; right now.&lt;/p&gt;

&lt;p&gt;You keep:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;rootless security posture&lt;/li&gt;
&lt;li&gt;declarative, reviewable config&lt;/li&gt;
&lt;li&gt;native systemd lifecycle + logs&lt;/li&gt;
&lt;li&gt;built-in update workflow with rollback support&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s a solid production baseline for single-host services.&lt;/p&gt;




&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Podman Quadlet and systemd unit integration (&lt;code&gt;podman-systemd.unit&lt;/code&gt;): &lt;a href="https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html" rel="noopener noreferrer"&gt;https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Podman Quadlet CLI (&lt;code&gt;podman-quadlet&lt;/code&gt;): &lt;a href="https://docs.podman.io/en/latest/markdown/podman-quadlet.1.html" rel="noopener noreferrer"&gt;https://docs.podman.io/en/latest/markdown/podman-quadlet.1.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;podman generate systemd&lt;/code&gt; deprecation notice: &lt;a href="https://docs.podman.io/en/latest/markdown/podman-generate-systemd.1.html" rel="noopener noreferrer"&gt;https://docs.podman.io/en/latest/markdown/podman-generate-systemd.1.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Podman auto-update behavior + systemd timer: &lt;a href="https://docs.podman.io/en/latest/markdown/podman-auto-update.1.html" rel="noopener noreferrer"&gt;https://docs.podman.io/en/latest/markdown/podman-auto-update.1.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;systemd timer semantics (&lt;code&gt;RemainAfterExit&lt;/code&gt;, &lt;code&gt;Persistent&lt;/code&gt;, &lt;code&gt;RandomizedDelaySec&lt;/code&gt;, etc.): &lt;a href="https://man7.org/linux/man-pages/man5/systemd.timer.5.html" rel="noopener noreferrer"&gt;https://man7.org/linux/man-pages/man5/systemd.timer.5.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Caddy reverse proxy directive docs: &lt;a href="https://caddyserver.com/docs/caddyfile/directives/reverse_proxy" rel="noopener noreferrer"&gt;https://caddyserver.com/docs/caddyfile/directives/reverse_proxy&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>linux</category>
      <category>devops</category>
      <category>systemd</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>APT Pinning Without Breaking Your Box: Practical `preferences.d` Patterns for Debian/Ubuntu</title>
      <dc:creator>Lyra</dc:creator>
      <pubDate>Mon, 15 Jun 2026 19:40:32 +0000</pubDate>
      <link>https://dev.to/lyraalishaikh/apt-pinning-without-breaking-your-box-practical-preferencesd-patterns-for-debianubuntu-586k</link>
      <guid>https://dev.to/lyraalishaikh/apt-pinning-without-breaking-your-box-practical-preferencesd-patterns-for-debianubuntu-586k</guid>
      <description>&lt;p&gt;If you run Debian or Ubuntu long enough, you eventually hit the same tension:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the distro version is stable and boring, which is good&lt;/li&gt;
&lt;li&gt;one package is too old, which is annoying&lt;/li&gt;
&lt;li&gt;a third-party repo solves that problem, which is dangerous&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is exactly where APT pinning helps.&lt;/p&gt;

&lt;p&gt;Done well, pinning lets you &lt;strong&gt;prefer the packages you trust&lt;/strong&gt;, &lt;strong&gt;pull only the packages you actually want&lt;/strong&gt;, and &lt;strong&gt;avoid surprise upgrades from the wrong repository&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Done badly, it turns package management into archaeology.&lt;/p&gt;

&lt;p&gt;This guide stays on the safe side: practical patterns, complete examples, and verification steps you can run before changing anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  What APT pinning actually does
&lt;/h2&gt;

&lt;p&gt;APT assigns a priority to each available package version. When more than one version exists, APT normally chooses the candidate with the highest effective priority.&lt;/p&gt;

&lt;p&gt;A few facts matter immediately:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;installed versions usually have priority &lt;strong&gt;100&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;uninstalled versions from normal repositories usually have priority &lt;strong&gt;500&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;a target release can get priority &lt;strong&gt;990&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;priorities &lt;strong&gt;above 1000&lt;/strong&gt; can force a downgrade&lt;/li&gt;
&lt;li&gt;priorities &lt;strong&gt;below 0&lt;/strong&gt; prevent installation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That behavior is documented in &lt;code&gt;apt_preferences(5)&lt;/code&gt; and is the reason pinning is powerful enough to help or hurt.&lt;/p&gt;

&lt;h2&gt;
  
  
  First: inspect your current priorities
&lt;/h2&gt;

&lt;p&gt;Before writing any pin, look at what APT already believes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apt-cache policy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That shows repository-level priorities.&lt;/p&gt;

&lt;p&gt;To inspect a specific package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apt-cache policy neovim
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example output will look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;neovim:
  Installed: 0.7.2-7
  Candidate: 0.7.2-7
  Version table:
     0.9.5-6~bpo12+1 100
        100 https://deb.debian.org/debian bookworm-backports/main amd64 Packages
 *** 0.7.2-7 500
        500 https://deb.debian.org/debian bookworm/main amd64 Packages
        100 /var/lib/dpkg/status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That tells you three useful things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;what is installed&lt;/li&gt;
&lt;li&gt;what APT would install next (&lt;code&gt;Candidate&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;why it chose that version&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you skip this step, you are guessing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where pin files live
&lt;/h2&gt;

&lt;p&gt;Use files under:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/etc/apt/preferences.d/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example naming:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/etc/apt/preferences.d/80-backports-neovim.pref
/etc/apt/preferences.d/90-vendor-limit.pref
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep them small and descriptive. One purpose per file is easier to reason about and easier to undo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 1: prefer one package from backports
&lt;/h2&gt;

&lt;p&gt;This is the safest and most common use case.&lt;/p&gt;

&lt;p&gt;You want a newer version of one package, but you do &lt;strong&gt;not&lt;/strong&gt; want your entire system drifting toward backports.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: confirm the package exists there
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apt update
apt-cache policy neovim
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: create a package-specific pin
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo install&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; 0755 /etc/apt/preferences.d
&lt;span class="nb"&gt;sudo tee&lt;/span&gt; /etc/apt/preferences.d/80-neovim-backports.pref &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
Package: neovim
Pin: release a=bookworm-backports
Pin-Priority: 900
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: refresh and verify
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
apt-cache policy neovim
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should now see the backports version become the candidate.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: install it
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;neovim
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why &lt;code&gt;900&lt;/code&gt;?
&lt;/h3&gt;

&lt;p&gt;A priority of &lt;code&gt;900&lt;/code&gt; says:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;prefer this source strongly&lt;/li&gt;
&lt;li&gt;but do &lt;strong&gt;not&lt;/strong&gt; cross the line into forced downgrade behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is usually what you want for a package-specific preference.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 2: block automatic upgrades from a third-party repo, but allow one package
&lt;/h2&gt;

&lt;p&gt;This is the pattern I trust most when adding vendor repositories.&lt;/p&gt;

&lt;p&gt;Say you add a repo because you need &lt;strong&gt;one&lt;/strong&gt; package. You do not want the repo silently replacing unrelated distro packages later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: inspect the repo identity
&lt;/h3&gt;

&lt;p&gt;Run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apt-cache policy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look for the repository lines and note values such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;origin&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;o=&lt;/code&gt; (Origin from the Release file)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;a=&lt;/code&gt; (archive)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;n=&lt;/code&gt; (codename)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;l=&lt;/code&gt; (label)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 2: set a low default priority for that repo
&lt;/h3&gt;

&lt;p&gt;Replace the example values below with what &lt;code&gt;apt-cache policy&lt;/code&gt; actually shows on your machine.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo tee&lt;/span&gt; /etc/apt/preferences.d/90-example-vendor-limit.pref &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
Package: *
Pin: origin packages.example.com
Pin-Priority: 1

Package: example-agent
Pin: origin packages.example.com
Pin-Priority: 700
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What this does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;every package from that repo is effectively deprioritized&lt;/li&gt;
&lt;li&gt;except &lt;code&gt;example-agent&lt;/code&gt;, which is allowed to upgrade normally&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
apt-cache policy example-agent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also test another package that exists in both the distro repo and the third-party repo. The distro version should still win.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 3: explicitly prevent a package version or source
&lt;/h2&gt;

&lt;p&gt;Sometimes you do not want a package coming from a specific place at all.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo tee&lt;/span&gt; /etc/apt/preferences.d/90-no-unstable-foo.pref &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
Package: foo
Pin: release a=unstable
Pin-Priority: -1
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That tells APT not to install &lt;code&gt;foo&lt;/code&gt; from &lt;code&gt;unstable&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A negative priority is stronger than “unlikely”; it is “don’t install this candidate.”&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 4: one-time install from another repo without permanent preference
&lt;/h2&gt;

&lt;p&gt;Sometimes you do &lt;strong&gt;not&lt;/strong&gt; need persistent pinning. You just need a one-off install.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; bookworm-backports neovim
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That can be cleaner than a permanent pin if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you are testing&lt;/li&gt;
&lt;li&gt;you only need one install once&lt;/li&gt;
&lt;li&gt;you do not want future upgrades to keep following that repo automatically&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is often the better first move.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;apt-mark hold&lt;/code&gt; is not the same thing
&lt;/h2&gt;

&lt;p&gt;A lot of guides blur these together. They are different tools.&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;apt-mark hold&lt;/code&gt; when you want to freeze a package in place:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-mark hold docker-ce
apt-mark showhold
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use pinning when you want APT to &lt;strong&gt;prefer or avoid a source/version according to policy&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;hold&lt;/strong&gt; = “do not change this package automatically”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;pinning&lt;/strong&gt; = “choose candidates according to these repository/version rules”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They can be combined, but they solve different problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  A safe workflow for creating pins
&lt;/h2&gt;

&lt;p&gt;Here is the workflow I recommend on real machines.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Update metadata
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Inspect repo and package priorities
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apt-cache policy
apt-cache policy neovim
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Add the smallest possible pin
&lt;/h3&gt;

&lt;p&gt;Prefer package-specific pins over broad repo-wide pins unless you genuinely need repo-wide behavior.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Refresh APT metadata
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Verify the candidate changed the way you expected
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apt-cache policy neovim
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  6. Simulate before installing
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nb"&gt;install &lt;/span&gt;neovim
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;-s&lt;/code&gt; simulation is cheap insurance. It lets you inspect dependency changes before they happen.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common mistakes that cause pain
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Pinning too broadly
&lt;/h3&gt;

&lt;p&gt;This is the classic foot-gun:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Package: *
Pin: release a=testing
Pin-Priority: 900
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That may be intentional in a lab. On a stable production machine, it is how you slowly stop having a stable production machine.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Using &lt;code&gt;1001&lt;/code&gt; casually
&lt;/h3&gt;

&lt;p&gt;A priority above &lt;code&gt;1000&lt;/code&gt; can force downgrades. That is sometimes useful, but it is sharp enough that you should treat it like a loaded tool.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;900&lt;/code&gt; solves the problem, use &lt;code&gt;900&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Not checking &lt;code&gt;apt-cache policy&lt;/code&gt; before and after
&lt;/h3&gt;

&lt;p&gt;If you do not verify the candidate package, you have not confirmed that the policy works.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Mixing releases without understanding the compatibility risk
&lt;/h3&gt;

&lt;p&gt;Pinning controls selection, but it does &lt;strong&gt;not&lt;/strong&gt; magically make packages from different releases compatible.&lt;/p&gt;

&lt;p&gt;Debian’s own documentation is pretty direct here: mixing releases carelessly can produce uninstallable or broken systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rollback: how to undo a pin cleanly
&lt;/h2&gt;

&lt;p&gt;If a pin causes trouble, remove the file and refresh APT.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo rm&lt;/span&gt; /etc/apt/preferences.d/80-neovim-backports.pref
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
apt-cache policy neovim
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you also want to move back to the distro version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;neovim/bookworm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or install an explicit version after checking what is available:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apt-cache policy neovim
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;&lt;span class="nv"&gt;neovim&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.7.2-7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the package was held:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-mark unhold docker-ce
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  My practical rules of thumb
&lt;/h2&gt;

&lt;p&gt;If you want the short version, this is mine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;use &lt;strong&gt;one-off &lt;code&gt;-t&lt;/code&gt; installs&lt;/strong&gt; for experiments&lt;/li&gt;
&lt;li&gt;use &lt;strong&gt;package-specific pins&lt;/strong&gt; for long-term exceptions&lt;/li&gt;
&lt;li&gt;keep &lt;strong&gt;third-party repos low priority by default&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;avoid broad cross-release pinning on stable systems&lt;/li&gt;
&lt;li&gt;check &lt;code&gt;apt-cache policy&lt;/code&gt; before and after every change&lt;/li&gt;
&lt;li&gt;simulate upgrades with &lt;code&gt;apt -s&lt;/code&gt; when the package is important&lt;/li&gt;
&lt;li&gt;reserve &lt;code&gt;Pin-Priority: 1001&lt;/code&gt; for deliberate, documented cases&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;APT pinning is one of those Linux tools that rewards restraint. The goal is not to outsmart the package manager. The goal is to make your intent explicit enough that future-you can still trust the machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Debian manpages: &lt;code&gt;apt_preferences(5)&lt;/code&gt; — &lt;a href="https://manpages.debian.org/testing/apt/apt_preferences.5.en.html" rel="noopener noreferrer"&gt;https://manpages.debian.org/testing/apt/apt_preferences.5.en.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Debian Wiki: AptConfiguration — &lt;a href="https://wiki.debian.org/AptConfiguration" rel="noopener noreferrer"&gt;https://wiki.debian.org/AptConfiguration&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Debian manpages: &lt;code&gt;apt-cache(8)&lt;/code&gt; — &lt;a href="https://manpages.debian.org/testing/apt/apt-cache.8.en.html" rel="noopener noreferrer"&gt;https://manpages.debian.org/testing/apt/apt-cache.8.en.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Debian manpages: &lt;code&gt;apt-mark(8)&lt;/code&gt; — &lt;a href="https://manpages.debian.org/testing/apt/apt-mark.8.en.html" rel="noopener noreferrer"&gt;https://manpages.debian.org/testing/apt/apt-mark.8.en.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Debian Administrator’s Handbook, &lt;code&gt;apt-cache policy&lt;/code&gt; examples — &lt;a href="https://www.debian.org/doc/manuals/debian-handbook/sect.apt-cache.en.html" rel="noopener noreferrer"&gt;https://www.debian.org/doc/manuals/debian-handbook/sect.apt-cache.en.html&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>linux</category>
      <category>opensource</category>
      <category>devops</category>
      <category>automation</category>
    </item>
  </channel>
</rss>
