<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ Linux - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ Linux - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Thu, 08 Oct 2026 15:34:16 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/linux/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Write a Linux Kernel Module That Actually Builds ]]>
                </title>
                <description>
                    <![CDATA[ A Linux kernel module is a small piece of code that can be loaded into the running kernel without rebuilding the entire kernel. That sounds simple enough, but even a minimal module produces a surprisi ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-write-a-linux-kernel-module-that-actually-builds/</link>
                <guid isPermaLink="false">6aa9b46181fb07630380c2e8</guid>
                
                    <category>
                        <![CDATA[ Kernel ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Linux ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Chris Roy ]]>
                </dc:creator>
                <pubDate>Tue, 15 Sep 2026 21:10:57 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/f8a7aabd-8494-42d5-86a1-e1eb51c2d0a7.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>A Linux <code>kernel module</code> is a small piece of code that can be loaded into the running kernel without rebuilding the entire kernel.</p>
<p>That sounds simple enough, but even a minimal module produces a surprising amount of machinery around it: object files, metadata, exported and unresolved symbols, and a final <code>.ko</code> file that is quite different from an ordinary executable.</p>
<p>Here's a complete, working Linux kernel module. It's just twenty-two lines, seven of which are includes and metadata:</p>
<pre><code class="language-c">#include &lt;linux/init.h&gt;
#include &lt;linux/module.h&gt;
#include &lt;linux/kernel.h&gt;

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Chris Roy");
MODULE_DESCRIPTION("A minimal loadable kernel module");
MODULE_VERSION("0.1");

static int __init hello_init(void)
{
    pr_info("hello: loaded, module at %pS\n", hello_init);
    return 0;
}

static void __exit hello_exit(void)
{
    pr_info("hello: unloaded\n");
}

module_init(hello_init);
module_exit(hello_exit);
</code></pre>
<p>Compiled on the machine I'm writing this on, that produces a file of about 106,000 bytes. Strip the debug information out and the same module is 4,864 bytes. Ninety-five percent of what the build gave you isn't code.</p>
<p>Your total will differ from mine, and not by a predictable amount. Part of it is where you built: the debug information records the directory you compiled in, so a deeply nested path costs a few hundred bytes that a short one doesn't. Your compiler version and kernel configuration move it further. The proportion is what holds. The exact byte count is only what this machine produced.</p>
<p>That gap is a good place to start, because most kernel module tutorials show you the listing above, tell you to run <code>make</code>, and stop.</p>
<p>This one follows what the build actually produced, what your module already depends on before you wrote anything useful, and why the tutorial you found from 2014 no longer compiles.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-what-you-need">What You Need</a></p>
</li>
<li><p><a href="#heading-the-smallest-module-that-works">The Smallest Module That Works</a></p>
</li>
<li><p><a href="#heading-the-makefile-is-stranger-than-it-looks">The Makefile is Stranger Than it Looks</a></p>
</li>
<li><p><a href="#heading-what-the-build-actually-did">What the Build Actually Did</a></p>
</li>
<li><p><a href="#heading-whats-inside-a-ko-file">What's Inside a .ko File</a></p>
</li>
<li><p><a href="#heading-your-hello-world-already-depends-on-three-things">Your Hello World Already Depends on Three Things</a></p>
</li>
<li><p><a href="#heading-vermagic-and-why-your-module-refuses-to-load">vermagic, and Why Your Module Refuses to Load</a></p>
</li>
<li><p><a href="#heading-passing-parameters-at-load-time">Passing Parameters at Load Time</a></p>
</li>
<li><p><a href="#heading-loading-it-and-where-the-output-goes">Loading it, and Where the Output Goes</a></p>
</li>
<li><p><a href="#heading-four-build-errors-and-what-they-mean">Four Build Errors and What They Mean</a></p>
</li>
<li><p><a href="#heading-why-the-tutorial-you-found-doesnt-compile">Why the Tutorial You Found Doesn't Compile</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
<li><p><a href="#heading-epilogue">Epilogue</a></p>
</li>
</ul>
<h2 id="heading-what-you-need">What You Need</h2>
<p>To follow along here, you'll need a Linux machine you're willing to load code into, the headers for the kernel you're running, and a compiler.</p>
<p>On Debian or Ubuntu:</p>
<pre><code class="language-bash">sudo apt install build-essential linux-headers-$(uname -r)
</code></pre>
<p>On Fedora, the equivalent is <code>kernel-devel</code> and <code>kernel-headers</code>, and on Arch it's the <code>linux-headers</code> package matching your kernel.</p>
<p>Check that the headers landed where the build expects them:</p>
<pre><code class="language-bash">ls -d /lib/modules/$(uname -r)/build
</code></pre>
<p>That path is a symlink into the headers package, and its absence is the single most common reason a module build fails with an error that mentions nothing about headers.</p>
<p>Two things will stop you from loading a module even after it builds. Secure Boot rejects unsigned modules, and kernel lockdown blocks loading in confidentiality mode. Check both:</p>
<pre><code class="language-bash">mokutil --sb-state
cat /sys/kernel/security/lockdown
</code></pre>
<p>On the machine here, Secure Boot is disabled and lockdown reports <code>[none] integrity confidentiality</code>, with the brackets marking the active mode. If yours shows Secure Boot enabled, you'll need to sign the module or disable Secure Boot in firmware before it will load.</p>
<p>I'm on Ubuntu 22.04 with kernel 5.15.0-190-generic and gcc 11.4. Your versions will differ, and the article says where that matters.</p>
<h2 id="heading-the-smallest-module-that-works">The Smallest Module That Works</h2>
<p>Save the code from the beginning of this article as <code>hello.c</code>. Here it is again for reference:</p>
<pre><code class="language-c">#include &lt;linux/init.h&gt;
#include &lt;linux/module.h&gt;
#include &lt;linux/kernel.h&gt;

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Chris Roy");
MODULE_DESCRIPTION("A minimal loadable kernel module");
MODULE_VERSION("0.1");

static int __init hello_init(void)
{
    pr_info("hello: loaded, module at %pS\n", hello_init);
    return 0;
}

static void __exit hello_exit(void)
{
    pr_info("hello: unloaded\n");
}

module_init(hello_init);
module_exit(hello_exit);
</code></pre>
<p>Four things in it are doing real work.</p>
<p><code>module_init</code> and <code>module_exit</code> register the functions the kernel calls when your module is loaded and unloaded. They aren't <code>main</code>. A module has no single entry point that runs and returns. It has hooks that fire on two specific events, and it does nothing in between unless something else calls into it.</p>
<p><code>__init</code> and <code>__exit</code> are section markers. <code>__init</code> tells the kernel this code runs once and its memory can be freed afterward, which is why you'll see "Freeing unused kernel memory" in your boot log. <code>__exit</code> tells the build that this function is only needed if the module can be unloaded at all.</p>
<p><code>MODULE_LICENSE("GPL")</code> isn't paperwork. The kernel checks it at load time, and a module declaring a non-GPL license is denied access to symbols marked <code>EXPORT_SYMBOL_GPL</code>, which is most of the interesting ones. Omit the macro entirely and the kernel taints itself and logs a complaint.</p>
<p><code>pr_info</code> is the modern spelling of <code>printk(KERN_INFO ...)</code>. It writes to the kernel ring buffer, not to your terminal, which trips up nearly everyone the first time.</p>
<p>The <code>MODULE_AUTHOR</code>, <code>MODULE_DESCRIPTION</code>, and <code>MODULE_VERSION</code> macros are metadata rather than behavior, and they end up in the file for <code>modinfo</code> to read. Leave them out and nothing breaks, but recent kernels warn at build time about a missing <code>MODULE_DESCRIPTION</code>, which is reason enough to write all three from the start.</p>
<h2 id="heading-the-makefile-is-stranger-than-it-looks">The Makefile is Stranger Than it Looks</h2>
<pre><code class="language-makefile">obj-m += hello.o

all:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
</code></pre>
<p>This looks like a Makefile, and mostly isn't one. <code>obj-m += hello.o</code> isn't a Make variable you invented. It's a declaration read by kbuild, the kernel's own build system.</p>
<p>The <code>make -C</code> line changes directory into the kernel headers and runs the kernel's build system there, passing <code>M=$(PWD)</code> to say "the module source is over here." Your Makefile is a thin wrapper that hands the job to a build system you didn't write and can't easily replace.</p>
<p>That indirection is why module builds fail in ways that seem unrelated to your code. You're not compiling against the kernel headers the way you compile against libc headers. You're running the kernel's build, on your file, with its flags and its rules.</p>
<h2 id="heading-what-the-build-actually-did">What the Build Actually Did</h2>
<p>Run <code>make</code> and read the output rather than skipping it:</p>
<pre><code class="language-text">make -C /lib/modules/5.15.0-190-generic/build M=/home/chris/lkm modules
make[1]: Entering directory '/usr/src/linux-headers-5.15.0-190-generic'
  CC [M]  /home/chris/lkm/hello.o
  MODPOST /home/chris/lkm/Module.symvers
  CC [M]  /home/chris/lkm/hello.mod.o
  LD [M]  /home/chris/lkm/hello.ko
  BTF [M] /home/chris/lkm/hello.ko
Skipping BTF generation for /home/chris/lkm/hello.ko due to unavailability of vmlinux
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/6a783a81a29db580b40f1bc8/9e38d600-4a16-4f2d-8f45-8f4897f5f44c.png" alt="Diagram of the kernel module build pipeline: hello.c compiles to hello.o, MODPOST checks undefined symbols against the kernel export table and generates hello.mod.c, that compiles to hello.mod.o, the linker combines both into hello.ko at roughly 106 KB of which only 4,864 bytes survive stripping, and a final BTF step is skipped because Ubuntu ships no vmlinux" style="display: block;" width="600" height="400" loading="lazy">

<p>Five steps, and only the first is the compile you expected.</p>
<p><code>CC [M] hello.o</code> compiles your source. Ordinary.</p>
<p><code>MODPOST</code> is the step worth knowing about. It scans your object file for symbols you referenced but didn't define, checks each one against the kernel's table of exported symbols, and fails the build if you used something the kernel doesn't offer you. It also generates <code>hello.mod.c</code>, a small file of glue containing your module's metadata.</p>
<p><code>CC [M] hello.mod.o</code> compiles that generated glue, and <code>LD [M]</code> links it together with your object into the final <code>.ko</code>.</p>
<p><code>BTF [M]</code> would attach type information used by tracing tools. Here it was skipped, because generating BTF needs the uncompressed <code>vmlinux</code> image and Ubuntu doesn't ship it by default. The build warns and continues, which is correct: BTF is useful, not required.</p>
<h2 id="heading-whats-inside-a-ko-file">What's Inside a .ko File</h2>
<p>A <code>.ko</code> is an ordinary ELF object with kernel-specific sections bolted on. Look at its metadata:</p>
<pre><code class="language-bash">modinfo ./hello.ko
</code></pre>
<pre><code class="language-text">version:        0.1
description:    A minimal loadable kernel module
author:         Chris Roy
license:        GPL
srcversion:     39D86510C9FF65D797EAF90
depends:        
retpoline:      Y
name:           hello
vermagic:       5.15.0-190-generic SMP mod_unload modversions
</code></pre>
<p>All of that lives in one ELF section, stored as null-separated strings. You can read it straight out of the file:</p>
<pre><code class="language-bash">objcopy -O binary --only-section=.modinfo hello.ko /dev/stdout | tr '\0' '\n'
</code></pre>
<p>Which brings us back to the number from the opening. The module is about 106,000 bytes on disk:</p>
<pre><code class="language-bash">ls -l hello.ko
cp hello.ko /tmp/ &amp;&amp; strip --strip-debug /tmp/hello.ko &amp;&amp; ls -l /tmp/hello.ko
</code></pre>
<pre><code class="language-text">105984  hello.ko
  4864  /tmp/hello.ko
</code></pre>
<p>The actual module is under five kilobytes. Everything else is DWARF debug information the build keeps so that tools like <code>crash</code> and <code>gdb</code> can make sense of your code if it panics. When you load the module, the kernel doesn't load the debug sections, so the memory cost is the small number rather than the large one.</p>
<h2 id="heading-your-hello-world-already-depends-on-three-things">Your Hello World Already Depends on Three Things</h2>
<p>This is the part I'd have wanted someone to show me first. Ask the object what it needs from the kernel:</p>
<pre><code class="language-bash">nm -u hello.ko
</code></pre>
<pre><code class="language-text">U __fentry__
U _printk
U __x86_return_thunk
</code></pre>
<p><code>U</code> means undefined: symbols your module references and the kernel must supply at load time.</p>
<p><code>_printk</code> you can account for, since <code>pr_info</code> expands to it.</p>
<p><code>__fentry__</code> is a call the compiler placed at the top of every one of your functions, because the kernel is built with function tracing support. Every function you write in a module gets that hook whether you asked for it or not, and it's what lets <code>ftrace</code> instrument your code later without recompiling anything.</p>
<p><code>__x86_return_thunk</code> is a Spectre mitigation. Your compiler replaced ordinary return instructions with a call to a thunk that avoids the speculative execution path the vulnerability relies on. It appears in a module that prints one line, because the mitigation applies to all kernel code on this machine, module or not.</p>
<p>Two of the three symbols in your hello world are infrastructure the machine imposed on you. That's a fair picture of what writing kernel code is like.</p>
<p>MODPOST verified all three exist before the link succeeded. Had you called a function the kernel doesn't export, the build would have failed there with an "undefined symbol" error rather than producing a module that fails at load.</p>
<h2 id="heading-vermagic-and-why-your-module-refuses-to-load"><code>vermagic</code>, and Why Your Module Refuses to Load</h2>
<p>Look again at that line from <code>modinfo</code>:</p>
<pre><code class="language-text">vermagic: 5.15.0-190-generic SMP mod_unload modversions
</code></pre>
<p>The kernel compares that string against its own before loading anything, and refuses on a mismatch. It covers the release, whether the kernel is SMP, whether module unloading is compiled in, and whether symbol versioning is on.</p>
<p>This is why a module built on one machine usually won't load on another, and why upgrading your kernel means rebuilding your modules.</p>
<p>There's no ABI stability guarantee inside the Linux kernel. Internal structures change between releases, and a module compiled against one layout that ran against another would corrupt memory rather than fail cleanly. Refusing to load is the kernel being careful.</p>
<p>It's also why DKMS exists. If you have VirtualBox, ZFS, or an NVIDIA driver installed, you already have a module being rebuilt this way. On the machine here:</p>
<pre><code class="language-bash">modinfo vboxdrv | head -3
</code></pre>
<pre><code class="language-text">filename:       /lib/modules/5.15.0-190-generic/updates/dkms/vboxdrv.ko
version:        6.1.50_Ubuntu r161033 (0x00320000)
license:        GPL
</code></pre>
<p>Note the <code>updates/dkms/</code> in that path. DKMS keeps the source and rebuilds the module each time you install a new kernel, which is the maintenance cost the vermagic check makes unavoidable.</p>
<h2 id="heading-passing-parameters-at-load-time">Passing Parameters at Load Time</h2>
<p>A module that always does the same thing is rarely what you want. <code>module_param</code> exposes a variable so its value can be set at load time. Save this as <code>param.c</code> alongside <code>hello.c</code>:</p>
<pre><code class="language-c">#include &lt;linux/init.h&gt;
#include &lt;linux/module.h&gt;

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("A module that takes parameters");

static char *who = "world";
static int times = 1;

module_param(who, charp, 0444);
MODULE_PARM_DESC(who, "who to greet");
module_param(times, int, 0644);
MODULE_PARM_DESC(times, "how many times to greet");

static int __init param_init(void)
{
    int i;

    for (i = 0; i &lt; times; i++)
        pr_info("param: hello, %s\n", who);
    return 0;
}

static void __exit param_exit(void)
{
    pr_info("param: unloaded\n");
}

module_init(param_init);
module_exit(param_exit);
</code></pre>
<p>Add it to the Makefile, which takes a list:</p>
<pre><code class="language-makefile">obj-m += hello.o param.o
</code></pre>
<p>The three arguments are the variable, its type, and the permissions on the file that will represent it under <code>/sys/module/&lt;name&gt;/parameters/</code>. A mode of <code>0444</code> makes it readable and fixed once loaded. <code>0644</code> lets root write to that file and change the value while the module is running, which is useful. It's also how you introduce a race if the module reads the variable without expecting it to change.</p>
<p><code>charp</code> is a char pointer, and the other common types are <code>int</code>, <code>bool</code>, <code>long</code> and <code>charp</code> arrays via <code>module_param_array</code>. Pass a type that doesn't match the variable and the build fails rather than misbehaving later.</p>
<p><code>MODULE_PARM_DESC</code> puts the description into the module metadata, where <code>modinfo</code> finds it:</p>
<pre><code class="language-text">name:           param
parm:           who:who to greet (charp)
parm:           times:how many times to greet (int)
</code></pre>
<p>Set them at load time as <code>name=value</code> pairs:</p>
<pre><code class="language-bash">sudo insmod ./param.ko who=kernel times=3
</code></pre>
<p>Anyone can read what parameters a module accepts before loading it, which is the main reason to bother with <code>MODULE_PARM_DESC</code> at all.</p>
<h2 id="heading-loading-it-and-where-the-output-goes">Loading it, and Where the Output Goes</h2>
<pre><code class="language-bash">sudo insmod ./hello.ko
sudo dmesg | tail -2
lsmod | grep hello
sudo rmmod hello
</code></pre>
<p>The <code>pr_info</code> output goes to the kernel ring buffer, so it appears in <code>dmesg</code> rather than your terminal. If <code>dmesg</code> refuses without root, that's <code>kernel.dmesg_restrict</code>, and <code>sudo journalctl -k | tail</code> reads the same messages through the journal.</p>
<p>The <code>%pS</code> in the format string prints a kernel pointer as a symbol name and offset instead of a raw address, which is how you get something readable out of a log line rather than a hexadecimal number.</p>
<p><code>lsmod</code> reads <code>/proc/modules</code> and shows three columns: the module name, its size in memory, and a use count with the names of anything depending on it. A module with a non-zero use count can't be unloaded, which is the most common reason <code>rmmod</code> refuses.</p>
<p>One caution before you load anything. A bug in userspace crashes your program. But a bug here can take the machine down or corrupt a filesystem. Do this in a virtual machine the first several times. The cost of a snapshot is far lower than the cost of a corrupted disk.</p>
<h2 id="heading-four-build-errors-and-what-they-mean">Four Build Errors and What They Mean</h2>
<p>These four account for most of the time people lose, and each says something specific once you know what to read.</p>
<p><code>No rule to make target 'modules'. Stop.</code> The kernel headers are missing or the symlink at <code>/lib/modules/$(uname -r)/build</code> points nowhere. Install the headers package matching the exact kernel you're running, which is often not the newest one installed if you haven't rebooted since an update.</p>
<p><code>ERROR: modpost: "some_function" [hello.ko] undefined!</code> You referenced a symbol the kernel doesn't export. Here's what that looks like from a real build:</p>
<pre><code class="language-text">ERROR: modpost: "this_symbol_does_not_exist" [bad.ko] undefined!
make[2]: *** [scripts/Makefile.modpost:133: Module.symvers] Error 1
</code></pre>
<p>Retrying won't help. Either the function is internal to the kernel and never exported, or it's exported with <code>EXPORT_SYMBOL_GPL</code> and your module declares a non-GPL license. Check with <code>grep the_symbol /proc/kallsyms</code>, where a capital <code>T</code> in the second column means it's a global text symbol.</p>
<p><code>insmod: ERROR: could not insert module: Invalid module format</code>: The build succeeded but vermagic doesn't match the running kernel. Compare <code>modinfo ./hello.ko | grep vermagic</code> against <code>uname -r</code>. Rebuilding against the correct headers fixes it.</p>
<p><code>insmod: ERROR: could not insert module: Operation not permitted</code>: Usually Secure Boot rejecting an unsigned module, or lockdown in confidentiality mode. Check <code>mokutil --sb-state</code> and <code>cat /sys/kernel/security/lockdown</code> before assuming your code is at fault.</p>
<p>One more thing, which is easier to learn now than to debug later. Loading any out-of-tree module sets a taint flag on the kernel, which is recorded and reported in any subsequent oops or panic:</p>
<pre><code class="language-bash">cat /proc/sys/kernel/tainted
</code></pre>
<p>The value is a bitmask. It reads 4096 on the machine here, which is bit 12, <code>TAINT_OOT_MODULE</code>, meaning an out-of-tree module has been loaded at some point.</p>
<p>Bit 13 is the neighboring one people confuse it with, <code>TAINT_UNSIGNED_MODULE</code>, which is what Secure Boot cares about.</p>
<p>The full list is in <code>include/linux/panic.h</code> in the kernel source. Kernel developers will ask you to reproduce a bug on an untainted kernel before they look at it, and this is the file that tells them whether you did.</p>
<h2 id="heading-why-the-tutorial-you-found-doesnt-compile">Why the Tutorial You Found Doesn't Compile</h2>
<p>Most module tutorials on the web predate several changes, and these are the ones that bite.</p>
<p><code>printk(KERN_INFO "...")</code> still works, but <code>pr_info</code> is the current spelling and carries the log level for you.</p>
<p><code>init_module</code> and <code>cleanup_module</code> as bare function names were the old convention. Use <code>module_init</code> and <code>module_exit</code> with your own names instead, which lets a file define both without collisions.</p>
<p><code>MODULE_LICENSE</code> was once optional in practice. It's now load-bearing, since it gates access to GPL-only exported symbols.</p>
<p>The <code>M=</code> argument used to be spelled <code>SUBDIRS=</code>. That spelling was removed, and a tutorial using it fails with an error that doesn't mention <code>SUBDIRS</code> anywhere.</p>
<p>Header paths moved. Anything referring to <code>/usr/src/linux</code> predates the split into per-kernel headers packages and is old enough that the rest of it needs checking too. A smaller sign: <code>&lt;linux/module.h&gt;</code> has included <code>&lt;linux/moduleparam.h&gt;</code> for years, so a tutorial that carefully includes both is copying from something old, even though including both is harmless.</p>
<p>If a tutorial builds without warnings on your kernel, it's current enough. If it doesn't, the kernel version it targeted is usually printed in the first error.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>You can now build a kernel module, read what the build produced, and explain every symbol it depends on.</p>
<p>More usefully, you know why it fails in the specific ways it does. A missing <code>/lib/modules/$(uname -r)/build</code> is a headers problem. A vermagic mismatch is a rebuild. An undefined symbol at MODPOST means the kernel doesn't export what you asked for, and no amount of retrying will change that.</p>
<p>There are a few directions to go from here. Register a <code>/proc</code> entry with <code>proc_create</code> and read from it, which is the smallest useful thing a module can do.</p>
<p>Read the kernel's own <code>Module.symvers</code> under <code>/usr/src/linux-headers-$(uname -r)/</code> to see the table MODPOST checked against, which is 26,420 exported symbols on this machine and marks each one <code>EXPORT_SYMBOL</code> or <code>EXPORT_SYMBOL_GPL</code>.</p>
<p>Or trace your own module's functions, which works because of the <code>__fentry__</code> hook that was there from the first build. There's no <code>ftrace</code> command to run. It's an interface under <code>/sys/kernel/tracing</code>, so you drive it by writing to files:</p>
<pre><code class="language-bash">sudo sh -c 'echo hello_init &gt; /sys/kernel/tracing/set_ftrace_filter'
sudo sh -c 'echo function &gt; /sys/kernel/tracing/current_tracer'
sudo cat /sys/kernel/tracing/trace
</code></pre>
<p>On older systems, that path is <code>/sys/kernel/debug/tracing</code> instead. If you would rather not write to files by hand, <code>trace-cmd</code> wraps the same interface.</p>
<h2 id="heading-epilogue">Epilogue</h2>
<p>Everything above assumes you're allowed to do it, and that assumption is the part I find interesting. A loaded module runs with the same authority as the kernel itself. It can read any memory, patch any function, and ignore any policy the system thought it was enforcing, because by the time it runs there's nothing left above it to say no.</p>
<p>That makes module loading the one operation a permission model can't contain, which is why the kernel guards it with signatures and lockdown rather than with permissions.</p>
<p>I ran into that floor while working on a capability-backed desktop OS, one where a program's manifest is the whole of what it may do, and the Debian ecosystem still has to work underneath it. Modules are where that model stops being expressible, so working out exactly what the kernel checks before accepting one stopped being a detail and became a design constraint.</p>
<p>I write about systems and their mysteries at <a href="https://thechris.in">thechris.in</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Sandbox a Linux Process with Landlock, No Root Required ]]>
                </title>
                <description>
                    <![CDATA[ Here's a program restricting itself, then trying to read two files: without landlock:   read /etc/hostname            ok   read /tmp/secret.txt          ok with landlock, /etc allowed:   read ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-sandbox-a-linux-process-with-landlock-no-root-required/</link>
                <guid isPermaLink="false">6aa475c369d3adf4a48d4cc9</guid>
                
                    <category>
                        <![CDATA[ Kernel ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Linux ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Chris Roy ]]>
                </dc:creator>
                <pubDate>Fri, 11 Sep 2026 21:42:27 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/d2e4d0c7-23b4-4d74-a41c-8102fa298e6a.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Here's a program restricting itself, then trying to read two files:</p>
<pre><code class="language-text">without landlock:
  read /etc/hostname            ok
  read /tmp/secret.txt          ok
with landlock, /etc allowed:
  read /etc/hostname            ok
  read /tmp/secret.txt          FAILED (Permission denied)
</code></pre>
<p>There's no root, no container, no configuration file, and no daemon. The program asked the kernel to take away its own access to most of the filesystem, and the kernel obliged.</p>
<p>That's Landlock, which has been in the kernel since 2021 without most people noticing. This article builds that program from nothing, runs it, and then walks into the four surprises that catch people the first time.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-what-you-need">What You Need</a></p>
</li>
<li><p><a href="#heading-where-landlock-sits">Where Landlock Sits</a></p>
</li>
<li><p><a href="#heading-three-system-calls-and-no-library">Three System Calls and No Library</a></p>
</li>
<li><p><a href="#heading-a-program-that-restricts-itself">A Program That Restricts Itself</a></p>
</li>
<li><p><a href="#heading-why-nonewprivs-is-mandatory">Why <code>no_new_privs</code> is Mandatory</a></p>
</li>
<li><p><a href="#heading-rulesets-intersect-they-never-widen">Rulesets Intersect, They Never Widen</a></p>
</li>
<li><p><a href="#heading-what-your-children-inherit">What Your Children Inherit</a></p>
</li>
<li><p><a href="#heading-the-exec-trap">The <code>exec</code> Trap</a></p>
</li>
<li><p><a href="#heading-wrapping-a-program-you-didnt-write">Wrapping a Program You Didn't Write</a></p>
</li>
<li><p><a href="#heading-finding-the-paths-a-program-needs">Finding the Paths a Program Needs</a></p>
</li>
<li><p><a href="#heading-which-abi-version-you-have">Which ABI Version You Have</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
<li><p><a href="#heading-epilogue">Epilogue</a></p>
</li>
</ul>
<h2 id="heading-what-you-need">What You Need</h2>
<p>To follow along, you'll need a kernel of 5.13 or newer, the standard headers, and a C compiler. Nothing else, and notably not root.</p>
<pre><code class="language-bash">grep landlock /sys/kernel/security/lsm
ls /usr/include/linux/landlock.h
</code></pre>
<p>The first command matters. Landlock can be compiled into a kernel and still be inactive, because Linux Security Modules have to be enabled at boot. On this machine, that file reads <code>lockdown,capability,landlock,yama,apparmor</code>.</p>
<p>If <code>landlock</code> is missing from yours, add <code>lsm=landlock,</code> to the front of the existing list in your kernel command line and reboot. Ubuntu has shipped it enabled since 22.04, and current Fedora and Arch kernels carry it too, but the <code>grep</code> above is the only answer that counts for your machine.</p>
<p>Everything below was run on kernel 5.15.0-190-generic under Ubuntu 22.04.5, compiled with gcc 11.4, as an ordinary user with no sudo anywhere.</p>
<h2 id="heading-where-landlock-sits">Where Landlock Sits</h2>
<p>Linux Security Modules are a framework, not a policy. The kernel calls out to LSM hooks at decision points, before opening a file, creating a process, or mapping executable memory, and whatever modules are loaded get to say yes or no.</p>
<p>SELinux and AppArmor are the two most people have heard of, and both are administrator tools: someone with root writes a policy, the system loads it, and your program lives inside whatever that policy says.</p>
<p>Landlock inverts that. It's the first LSM a process can apply to itself, without privilege, at runtime. You don't need to convince an administrator that your program deserves a policy. The program asks for less than it currently has, and the kernel narrows it.</p>
<p>That "asks for less" is the whole design. Landlock can only ever remove access. There's no call that grants you something you didn't already have, which is precisely why it's safe to expose to unprivileged processes.</p>
<h2 id="heading-three-system-calls-and-no-library">Three System Calls and No Library</h2>
<p>Landlock is three syscalls and glibc wraps none of them, so you call them directly through <code>syscall()</code>:</p>
<pre><code class="language-c">static int create_ruleset(const struct landlock_ruleset_attr *attr)
{ return syscall(__NR_landlock_create_ruleset, attr, sizeof(*attr), 0); }

static int add_rule(int fd, const struct landlock_path_beneath_attr *pb)
{ return syscall(__NR_landlock_add_rule, fd, LANDLOCK_RULE_PATH_BENEATH, pb, 0); }

static int restrict_self(int fd)
{ return syscall(__NR_landlock_restrict_self, fd, 0); }
</code></pre>
<p><code>landlock_create_ruleset</code> declares which kinds of access you intend to govern and returns a file descriptor representing the ruleset. <code>landlock_add_rule</code> adds an exception in the form of a directory you want to keep. Finally, <code>landlock_restrict_self</code> applies the whole thing to the calling process, permanently.</p>
<p>The <code>handled_access_fs</code> field in the ruleset attribute is the part people get backwards. It doesn't list what you're allowing. It lists the access types this ruleset is responsible for, and anything in that list is denied everywhere except the paths you explicitly add. Handle read access and you lose read access to the entire filesystem until you add rules back.</p>
<h2 id="heading-a-program-that-restricts-itself">A Program That Restricts Itself</h2>
<p>Here is the whole thing:</p>
<pre><code class="language-c">#define _GNU_SOURCE
#include &lt;linux/landlock.h&gt;
#include &lt;sys/prctl.h&gt;
#include &lt;sys/syscall.h&gt;
#include &lt;fcntl.h&gt;
#include &lt;unistd.h&gt;
#include &lt;stdio.h&gt;
#include &lt;string.h&gt;
#include &lt;errno.h&gt;

#define READ_RIGHTS (LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR)

static int create_ruleset(const struct landlock_ruleset_attr *attr)
{ return syscall(__NR_landlock_create_ruleset, attr, sizeof(*attr), 0); }

static int add_rule(int fd, const struct landlock_path_beneath_attr *pb)
{ return syscall(__NR_landlock_add_rule, fd, LANDLOCK_RULE_PATH_BENEATH, pb, 0); }

static int restrict_self(int fd)
{ return syscall(__NR_landlock_restrict_self, fd, 0); }

static int allow_read(int ruleset_fd, const char *path)
{
    struct landlock_path_beneath_attr pb = { .allowed_access = READ_RIGHTS };
    int rc;

    pb.parent_fd = open(path, O_PATH | O_CLOEXEC);
    if (pb.parent_fd &lt; 0) { perror(path); return -1; }
    rc = add_rule(ruleset_fd, &amp;pb);
    close(pb.parent_fd);
    return rc;
}

static void try_read(const char *path)
{
    int fd = open(path, O_RDONLY);

    if (fd &lt; 0)
        printf("  read %-24s FAILED (%s)\n", path, strerror(errno));
    else
        { printf("  read %-24s ok\n", path); close(fd); }
}

int main(void)
{
    struct landlock_ruleset_attr attr = { .handled_access_fs = READ_RIGHTS };
    int ruleset_fd = create_ruleset(&amp;attr);

    if (ruleset_fd &lt; 0) { perror("landlock_create_ruleset"); return 1; }
    if (allow_read(ruleset_fd, "/etc") &lt; 0) return 1;

    if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) { perror("prctl"); return 1; }
    if (restrict_self(ruleset_fd)) { perror("landlock_restrict_self"); return 1; }
    close(ruleset_fd);

    printf("with landlock, /etc allowed:\n");
    try_read("/etc/hostname");
    try_read("/tmp/secret.txt");
    return 0;
}
</code></pre>
<p>Build and run it:</p>
<pre><code class="language-bash">gcc -Wall -o sandbox sandbox.c
echo "hunter2" &gt; /tmp/secret.txt
./sandbox
</code></pre>
<pre><code class="language-text">with landlock, /etc allowed:
  read /etc/hostname            ok
  read /tmp/secret.txt          FAILED (Permission denied)
</code></pre>
<p>Two details in there matter. The rule refers to a directory by an open file descriptor rather than a path string, opened with <code>O_PATH</code> so you get a handle without needing read permission on the directory itself. And <code>restrict_self</code> takes effect immediately for the calling process, with no way to undo it.</p>
<h2 id="heading-why-nonewprivs-is-mandatory">Why <code>no_new_privs</code> is Mandatory</h2>
<p>Take the <code>prctl</code> call out and the program stops working:</p>
<pre><code class="language-text">landlock_restrict_self -&gt; Operation not permitted
</code></pre>
<p>That's <code>EPERM</code>, and it's deliberate. <code>PR_SET_NO_NEW_PRIVS</code> tells the kernel that this process and its descendants can never gain privileges through <code>execve</code>, which is what stops a sandboxed process from escaping by running a setuid binary.</p>
<p>Without that guarantee, a restricted process could exec <code>sudo</code> or any setuid program and step outside the restrictions you just applied. Landlock refuses to apply itself at all rather than offer a sandbox with that hole in it. Set <code>no_new_privs</code> first, every time.</p>
<h2 id="heading-rulesets-intersect-they-never-widen">Rulesets Intersect, They Never Widen</h2>
<p>This is the property to get right. Apply a ruleset allowing <code>/etc</code>, then apply a second allowing <code>/tmp</code>, and ask what you can reach:</p>
<pre><code class="language-text">after first ruleset:  /etc=ok               /tmp=Permission denied
after second ruleset: /etc=Permission denied  /tmp=Permission denied
</code></pre>
<p>The second ruleset didn't add <code>/tmp</code>. It took away <code>/etc</code>, and left you with nothing.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a783a81a29db580b40f1bc8/3091ccb0-3d7e-4b52-8744-83a7b32848b9.png" alt="Diagram showing a Landlock sandbox narrowing in three steps: with no ruleset all five directories are readable, after a ruleset allowing /etc only /etc is readable, and after a second ruleset allowing /tmp nothing is readable at all, because the two rulesets intersect and their overlap is empty" style="display: block;" width="600" height="400" loading="lazy">

<p>Each <code>restrict_self</code> intersects with everything already applied. The first ruleset permitted <code>/etc</code> and denied the rest. The second permitted <code>/tmp</code> and denied the rest. What survives is the overlap of those two, which is empty.</p>
<p>So a Landlock sandbox is a ratchet. Every application can only tighten, never loosen, and there's no operation anywhere in the API that widens what a restricted process may do. If you need a process to have access to two directories, both rules go into one ruleset before you apply it.</p>
<p>That also means you can't change your mind. A long-running process that restricts itself early can't be granted more later, by itself or by anyone else, short of starting a new process.</p>
<h2 id="heading-what-your-children-inherit">What Your Children Inherit</h2>
<p>Restrictions follow <code>fork</code> without asking:</p>
<pre><code class="language-text">parent:       /etc=ok   /tmp/secret.txt=Permission denied
forked child: /etc=ok   /tmp/secret.txt=Permission denied
</code></pre>
<p>The child inherits the parent's Landlock domain exactly and there's no flag to opt out. The same holds across <code>execve</code>, which is the point of <code>no_new_privs</code>: the new program starts already inside the sandbox the old one built.</p>
<p>This is what makes Landlock useful for wrapping something you didn't write. Restrict yourself, then exec the thing you want contained, and it runs inside your restrictions without knowing they exist.</p>
<h2 id="heading-the-exec-trap">The <code>exec</code> Trap</h2>
<p>It also sets a trap. Take the program above, keep only <code>/etc</code> allowed, and try to exec anything:</p>
<pre><code class="language-text">allowed: /etc
execl(/usr/bin/cat) failed: Permission denied
</code></pre>
<p>Executing a binary requires reading it. This ruleset handles <code>LANDLOCK_ACCESS_FS_READ_FILE</code>, so the kernel checks whether <code>/usr/bin/cat</code> may be read, finds no rule covering <code>/usr</code>, and refuses before the program ever starts.</p>
<p>Add <code>/usr</code> to the same ruleset and it works:</p>
<pre><code class="language-text">allowed: /etc and /usr
devils-dell
</code></pre>
<p>The general lesson is that a Landlock sandbox has to include everything the process touches, and that set is larger than you think. Your binary, its interpreter, every shared library it loads, and any config it reads at startup. <code>ldd</code> on the binary is a good place to begin the list.</p>
<h2 id="heading-wrapping-a-program-you-didnt-write">Wrapping a Program You Didn't Write</h2>
<p>Inheritance across <code>exec</code> is what makes Landlock useful beyond your own code. Restrict yourself, then exec whatever you want contained, and it runs inside the sandbox without cooperating or even knowing.</p>
<p>A usable wrapper needs one addition to the program above. Handle <code>LANDLOCK_ACCESS_FS_EXECUTE</code> alongside the read rights, allow the system directories any binary needs, then allow whatever working directory the user asked for:</p>
<pre><code class="language-c">#define RIGHTS (LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR | \
                LANDLOCK_ACCESS_FS_EXECUTE)

static int add_path(int ruleset_fd, const char *path)
{
    struct landlock_path_beneath_attr pb = { .allowed_access = RIGHTS };
    int rc;

    pb.parent_fd = open(path, O_PATH | O_CLOEXEC);
    if (pb.parent_fd &lt; 0)
        return -1;
    rc = syscall(__NR_landlock_add_rule, ruleset_fd,
                 LANDLOCK_RULE_PATH_BENEATH, &amp;pb, 0);
    close(pb.parent_fd);
    return rc;
}

int main(int argc, char **argv)
{
    struct landlock_ruleset_attr attr = { .handled_access_fs = RIGHTS };
    const char *base[] = { "/usr", "/lib", "/lib64", "/bin", "/etc" };
    int fd, i;

    if (argc &lt; 3) { fprintf(stderr, "usage: %s DIR CMD...\n", argv[0]); return 2; }

    fd = syscall(__NR_landlock_create_ruleset, &amp;attr, sizeof(attr), 0);
    if (fd &lt; 0) { perror("create_ruleset"); return 1; }

    for (i = 0; i &lt; (int)(sizeof(base) / sizeof(*base)); i++)
        add_path(fd, base[i]);
    if (add_path(fd, argv[1]) &lt; 0) { perror(argv[1]); return 1; }

    if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) { perror("prctl"); return 1; }
    if (syscall(__NR_landlock_restrict_self, fd, 0)) { perror("restrict_self"); return 1; }
    close(fd);

    execvp(argv[2], &amp;argv[2]);
    fprintf(stderr, "%s: %s\n", argv[2], strerror(errno));
    return 1;
}
</code></pre>
<p>The loop ignores what <code>add_path</code> returns, so the same binary works on systems where <code>/lib64</code> or <code>/bin</code> are absent or symlinked somewhere else. The directory the user named is checked, because a typo there should be an error now rather than a puzzling denial later.</p>
<p>Now <code>cat</code> and <code>ls</code> can see the working directory and nothing else:</p>
<pre><code class="language-bash">mkdir -p /tmp/work &amp;&amp; echo "project data" &gt; /tmp/work/notes.txt

./llrun /tmp/work cat /tmp/work/notes.txt
./llrun /tmp/work cat /tmp/secret.txt
./llrun /tmp/work ls /home
</code></pre>
<pre><code class="language-text">project data
cat: /tmp/secret.txt: Permission denied
ls: cannot open directory '/home': Permission denied
</code></pre>
<p>Neither program was modified, recompiled, or asked for consent. <code>bwrap</code> and similar tools reach that outcome by building mount and user namespaces around the program. This gets there by asking one LSM for less, in about sixty lines, with nothing to install.</p>
<h2 id="heading-finding-the-paths-a-program-needs">Finding the Paths a Program Needs</h2>
<p>The hard part of any sandbox isn't the API. It's the list. Programs open far more than you expect, and a path you forget shows up as a failure somewhere deep in a run.</p>
<p>Two tools build the list for you. <code>ldd</code> gives the shared libraries, which must be readable or the program never starts:</p>
<pre><code class="language-bash">ldd /usr/bin/cat
</code></pre>
<pre><code class="language-text">linux-vdso.so.1 (0x00007fff85f39000)
libc.so.6 =&gt; /lib/x86_64-linux-gnu/libc.so.6 (0x00007f4f61bb5000)
/lib64/ld-linux-x86-64.so.2 (0x00007f4f61e0a000)
</code></pre>
<p><code>strace</code> gives everything else. Run the program unrestricted first and collect what it opens:</p>
<pre><code class="language-bash">strace -e trace=openat cat /etc/hostname 2&gt;&amp;1 | grep -oE '"/[^"]+"' | sort -u
</code></pre>
<pre><code class="language-text">"/etc/hostname"
"/etc/ld.so.cache"
"/lib/x86_64-linux-gnu/libc.so.6"
"/usr/lib/locale/locale-archive"
</code></pre>
<p>Four paths for a program that prints one file, and only one of them is the file you asked for. The linker cache, the C library, and the locale archive are all mandatory, which is why the wrapper allows <code>/usr</code>, <code>/lib</code> and <code>/etc</code> before it allows anything you chose.</p>
<p>Once restricted, <code>strace</code> also tells you exactly what a denial was:</p>
<pre><code class="language-bash">strace -f -e trace=openat ./llrun /tmp/work cat /tmp/secret.txt 2&gt;&amp;1 | grep EACCES
</code></pre>
<pre><code class="language-text">openat(AT_FDCWD, "/tmp/secret.txt", O_RDONLY) = -1 EACCES (Permission denied)
</code></pre>
<p>That's the loop. Run it, read the <code>EACCES</code> line, decide whether the path belongs in the ruleset or the program shouldn't be reaching for it, then repeat. Landlock logs nothing of its own on this kernel, so <code>strace</code> is the debugger. Kernels from 6.15 report denials through the audit subsystem, so check your version before hunting for a log that isn't there.</p>
<h2 id="heading-which-abi-version-you-have">Which ABI Version You Have</h2>
<p>Landlock has grown since 5.13, and features you read about may not exist on your kernel. Ask it directly:</p>
<pre><code class="language-c">int v = syscall(__NR_landlock_create_ruleset, NULL, 0,
                LANDLOCK_CREATE_RULESET_VERSION);
</code></pre>
<p>This machine reports <code>1</code>, which is the original from 5.13 and offers thirteen filesystem access rights:</p>
<pre><code class="language-bash">grep -oE "LANDLOCK_ACCESS_FS_[A-Z_]+" /usr/include/linux/landlock.h | sort -u
</code></pre>
<pre><code class="language-text">LANDLOCK_ACCESS_FS_EXECUTE      LANDLOCK_ACCESS_FS_MAKE_BLOCK
LANDLOCK_ACCESS_FS_MAKE_CHAR    LANDLOCK_ACCESS_FS_MAKE_DIR
LANDLOCK_ACCESS_FS_MAKE_FIFO    LANDLOCK_ACCESS_FS_MAKE_REG
LANDLOCK_ACCESS_FS_MAKE_SOCK    LANDLOCK_ACCESS_FS_MAKE_SYM
LANDLOCK_ACCESS_FS_READ_DIR     LANDLOCK_ACCESS_FS_READ_FILE
LANDLOCK_ACCESS_FS_REMOVE_DIR   LANDLOCK_ACCESS_FS_REMOVE_FILE
LANDLOCK_ACCESS_FS_WRITE_FILE
</code></pre>
<p>Later versions added file reparenting, truncation, network rules covering TCP bind and connect, and control over device ioctls. Each arrived in its own ABI bump, so a program that wants a newer right should query the version and degrade rather than assume. Passing a right the running kernel doesn't know about makes <code>landlock_create_ruleset</code> fail with <code>EINVAL</code>, which is a confusing error to debug if you haven't checked the version first.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>You can now sandbox a process from inside itself, with no privileges and no configuration, and you know the four surprises. <code>no_new_privs</code> comes first or nothing applies. Rulesets intersect rather than accumulate, so build one ruleset with everything in it. Children inherit, which is a feature. And read restrictions break <code>exec</code> unless the binary's path is allowed too.</p>
<p>There are a few directions to go from here. Add <code>LANDLOCK_ACCESS_FS_WRITE_FILE</code> to <code>handled_access_fs</code> and make a program that can read widely but write to exactly one directory. Wrap a program you didn't write by restricting yourself and then calling <code>execve</code>. Or look at how <code>strace</code> reports the denials, which is the fastest way to build the list of paths a real program actually needs.</p>
<h2 id="heading-epilogue">Epilogue</h2>
<p>The interesting question about Landlock isn't what it does. It's what it can't express. A ruleset names paths, so the unit of authority is a location in the filesystem rather than a particular file you were handed. You can say this process may read below <code>/etc</code>. You can't say this process may read the one file the user just picked, and nothing else.</p>
<p>That gap is what I've spent the last while on, building a capability-backed desktop OS in which authority arrives as a handle to one object rather than a rule about a location, with the Debian ecosystem still working underneath. Landlock does a great deal of the work, and the places it stops are where the design gets interesting.</p>
<p>I write about systems and their mysteries at <a href="https://thechris.in">thechris.in</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How Linux Actually Boots: From Firmware to the Login Screen ]]>
                </title>
                <description>
                    <![CDATA[ Open a terminal on any systemd machine and run this: systemd-analyze On the laptop I'm writing this on, it says: Startup finished in 5.855s (firmware) + 8.469s (loader) + 3.106s (kernel) + 12.181s (u ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-linux-actually-boots-from-firmware-to-the-login-screen/</link>
                <guid isPermaLink="false">6aa1ebfb3cc1af030bd4b974</guid>
                
                    <category>
                        <![CDATA[ Kernel ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Linux ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Chris Roy ]]>
                </dc:creator>
                <pubDate>Wed, 09 Sep 2026 23:30:03 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/de568fba-3b1b-4204-9585-60bbbd165fde.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Open a terminal on any systemd machine and run this:</p>
<pre><code class="language-bash">systemd-analyze
</code></pre>
<p>On the laptop I'm writing this on, it says:</p>
<pre><code class="language-text">Startup finished in 5.855s (firmware) + 8.469s (loader) + 3.106s (kernel)
+ 12.181s (userspace) = 29.613s
graphical.target reached after 12.175s in userspace
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/6a783a81a29db580b40f1bc8/a8ac7311-87f4-46ec-bb66-6c6b012295a9.png" alt="Stacked bar chart of a 29.6 second Linux boot, split into firmware 5.855s, bootloader 8.469s of which about 5 seconds is a GRUB keypress countdown, kernel 3.106s, and userspace 12.181s" style="display: block;" width="600" height="400" loading="lazy">

<p>Add those four and you get 29.611, not the 29.613 printed on the last line. That gap is real and it isn't a mistake: <code>systemd-analyze</code> truncates each phase for display while summing the underlying microseconds, so a couple of milliseconds hide in the rounding. It's a small thing, and it will save you an evening of hunting for a bug that isn't there.</p>
<p>Four numbers, and most people's mental model accounts for one of them. The kernel took three seconds. The bootloader before it took eight and a half, and the firmware before that took nearly six. Fourteen seconds of this machine's boot happened before Linux was running at all, and I chose a good part of it without noticing.</p>
<p>This article follows the Linux boot process the whole way through one real boot, from the firmware handing control to a bootloader to a login prompt on your screen. Everything here you can run yourself, and almost none of it needs root.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-what-you-need">What you need</a></p>
</li>
<li><p><a href="#heading-the-linux-boot-process-is-four-handoffs-not-one">The Linux boot process is four handoffs, not one</a></p>
</li>
<li><p><a href="#heading-firmware-and-the-part-linux-never-sees">Firmware, and the part Linux never sees</a></p>
</li>
<li><p><a href="#heading-the-bootloader-and-the-five-seconds-you-chose">The bootloader, and the five seconds you chose</a></p>
</li>
<li><p><a href="#heading-the-kernel-phase-three-seconds-to-a-working-machine">The kernel phase: three seconds to a working machine</a></p>
</li>
<li><p><a href="#heading-what-the-initramfs-is-and-the-chicken-and-egg-problem-it-solves">What the initramfs is, and the chicken-and-egg problem it solves</a></p>
</li>
<li><p><a href="#heading-pid-1-and-where-the-other-twelve-seconds-go">PID 1, and where the other twelve seconds go</a></p>
</li>
<li><p><a href="#heading-why-systemd-analyze-blame-misleads-you-about-boot-time">Why systemd-analyze blame misleads you about boot time</a></p>
</li>
<li><p><a href="#heading-reading-the-critical-chain">Reading the critical chain</a></p>
</li>
<li><p><a href="#heading-why-your-linux-boot-time-will-be-different">Why your Linux boot time will be different</a></p>
</li>
<li><p><a href="#heading-the-login-screen-and-the-handoff-to-you">The login screen, and the handoff to you</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
<li><p><a href="#heading-epilogue">Epilogue</a></p>
</li>
</ul>
<h2 id="heading-what-you-need">What You Need</h2>
<p>Any Linux machine running systemd, which covers Ubuntu, Debian, Fedora, Arch, and most things people install in 2026. A terminal. That's it.</p>
<p>The machine I'm measuring is Ubuntu 22.04.5 LTS running kernel 5.15.0-190-generic, booting in UEFI mode from an NVMe disk with an ext4 root filesystem, with systemd 249 as PID 1. Yours will differ, sometimes by a lot, and I'll say where to expect that.</p>
<p>One command needs root and one file is restricted on many systems. I'll flag both when we get there.</p>
<h2 id="heading-the-linux-boot-process-is-four-handoffs-not-one">The Linux Boot Process is Four Handoffs, Not One</h2>
<p>The word "boot" suggests a single process. But it's four, and they barely know about each other.</p>
<p>Firmware runs first, from a chip on the motherboard, and its job is to find something bootable and start it.</p>
<p>It then hands control to a bootloader and stops. The bootloader's job is to find a kernel, load it into memory along with an initial filesystem, and jump to it. It then stops.</p>
<p>The kernel brings up hardware, mounts a root filesystem, and starts exactly one userspace process. Then it stops being in charge, though it keeps servicing that process forever afterward.</p>
<p>That first process, PID 1, starts everything else.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a783a81a29db580b40f1bc8/4fbd3a3c-bc46-43e5-aa07-9c179d82a0d6.png" alt="Diagram of the Linux boot chain: UEFI firmware hands an EFI boot entry to GRUB, which loads vmlinuz plus initrd and the kernel command line, the kernel mounts the initramfs as a temporary root, switch_root hands off to systemd as PID 1, and systemd reaches graphical.target to start the LightDM login screen" style="display: block;" width="600" height="400" loading="lazy">

<p>Each handoff is one way. The firmware isn't sitting underneath Linux waiting to help. The bootloader is gone from memory. Hold onto that, because it explains why the four numbers in <code>systemd-analyze</code> are measured by different things and mean different things.</p>
<h2 id="heading-firmware-and-the-part-linux-never-sees">Firmware, and the Part Linux Never Sees</h2>
<p>The 5.855 seconds attributed to firmware is the only figure here that Linux didn't measure itself, and you can read the exact source it came from:</p>
<pre><code class="language-bash">cat /sys/firmware/acpi/fpdt/boot/bootloader_launch_ns
</code></pre>
<pre><code class="language-text">5855973785
</code></pre>
<p>That's 5.855973785 seconds, which is the 5.855 <code>systemd-analyze</code> reported, truncated for display. The firmware wrote it into an ACPI table called the Firmware Performance Data Table before handing over, and the kernel exposes the table's fields under that directory. Its sibling fills in the rest:</p>
<pre><code class="language-bash">cat /sys/firmware/acpi/fpdt/boot/exitbootservice_end_ns
</code></pre>
<p>On this machine that reads 14325507185, or 14.325 seconds, which is firmware and bootloader combined and matches the two phases added together.</p>
<p>Here's the part that trips people up. The gate isn't UEFI, it's whether your firmware publishes an FPDT at all. Plenty of UEFI machines don't, virtual machines especially, and on those <code>systemd-analyze</code> reports no firmware phase and no loader phase either, starting its accounting at the kernel. Check for the table rather than for UEFI:</p>
<pre><code class="language-bash">ls /sys/firmware/acpi/fpdt/boot/ 2&gt;/dev/null || echo "no FPDT, so no firmware timing"
</code></pre>
<p>If that prints nothing, your firmware isn't reporting, and there's no way to recover the number from inside Linux.</p>
<p>Almost six seconds is a lot, and there isn't much you can do about it from inside Linux. It's memory training and device enumeration, plus whatever your vendor decided to run before handing over. On a laptop with fast storage this is often the single largest phase, which surprises people who spend their optimization effort on services.</p>
<h2 id="heading-the-bootloader-and-the-five-seconds-you-chose">The Bootloader, and the Five Seconds You Chose</h2>
<p>Here's the number that changes how you read the whole output. The loader phase took 8.469 seconds, nearly three times what the kernel took. Almost none of that was work.</p>
<pre><code class="language-bash">grep ^GRUB_TIMEOUT /etc/default/grub
</code></pre>
<p>On this machine:</p>
<pre><code class="language-text">GRUB_TIMEOUT="5"
</code></pre>
<p>Five of those 8.469 seconds are GRUB counting down and waiting for a keypress that never comes. That's a configuration choice, made once, probably by the installer, and then never revisited. If you've ever wondered why your machine feels slow to start despite good hardware, this is the first place to look, and it's the cheapest fix in this entire article.</p>
<p>While GRUB is waiting, it already knows what it's going to do. You can read the instructions it passed along:</p>
<pre><code class="language-bash">cat /proc/cmdline
</code></pre>
<pre><code class="language-text">BOOT_IMAGE=/boot/vmlinuz-5.15.0-190-generic root=UUID=b4d0343e-9df4-40a7-be97-dcd51bdbf553
ro splash intel_iommu=on vt.handoff=7
</code></pre>
<p>That single line is the entire contract between the bootloader and the kernel. <code>BOOT_IMAGE</code> is which kernel got loaded. <code>root=UUID=...</code> names the filesystem to mount, by UUID rather than device name so it survives disks being renumbered. <code>ro</code> says mount it read-only at first. <code>splash</code> asks for a graphical boot screen instead of scrolling text. <code>intel_iommu=on</code> enables the IOMMU, and <code>vt.handoff=7</code> is Ubuntu passing the virtual terminal to the graphical stack without a flicker.</p>
<p>GRUB loads two files into memory: the kernel, and an initial filesystem image we'll come back to shortly. Then it jumps into the kernel and ceases to exist.</p>
<h2 id="heading-the-kernel-phase-three-seconds-to-a-working-machine">The Kernel Phase: Three Seconds to a Working Machine</h2>
<p>Now Linux is running. The kernel decompresses itself, sets up memory management, brings up the CPUs, initializes drivers, and looks for a root filesystem.</p>
<p>Watch it happen, with timestamps measured from the moment the kernel started:</p>
<pre><code class="language-bash">journalctl -k -b -o short-monotonic | head
</code></pre>
<pre><code class="language-text">[    0.000000] devils-dell kernel: microcode: microcode updated early to revision 0x100
[    0.000000] devils-dell kernel: Linux version 5.15.0-190-generic
[    0.000000] devils-dell kernel: Command line: BOOT_IMAGE=/boot/vmlinuz-5.15.0-190-generic
[    0.000000] devils-dell kernel: KERNEL supported cpus:
</code></pre>
<p>Zero is when the kernel began executing, not when you pressed the power button. Everything before this point, all fourteen seconds of firmware and bootloader, is outside this clock entirely. That's the first thing to understand about kernel boot timestamps, and it's why <code>dmesg</code> output makes some machines look far faster than they are.</p>
<p>You may have reached for <code>dmesg</code> there and been refused:</p>
<pre><code class="language-text">dmesg: read kernel buffer failed: Operation not permitted
</code></pre>
<p>That's deliberate, and you can confirm it:</p>
<pre><code class="language-bash">sysctl kernel.dmesg_restrict
</code></pre>
<p>Ubuntu sets <code>kernel.dmesg_restrict = 1</code>, which limits the kernel ring buffer to root because it leaks kernel addresses useful to an attacker. Use <code>journalctl -k</code> instead, which reads the same messages through the journal and works as an ordinary user.</p>
<p>One more detail from <code>/proc/cmdline</code> explains something people notice and rarely investigate. The kernel was told to mount the root filesystem <code>ro</code>, read-only, yet the system you're using now clearly writes to disk. Check what it looks like today:</p>
<pre><code class="language-bash">findmnt -n -o SOURCE,FSTYPE,OPTIONS /
</code></pre>
<pre><code class="language-text">/dev/nvme0n1p2 ext4 rw,relatime,errors=remount-ro
</code></pre>
<p>It's read-write now, so something changed it. The root filesystem is mounted read-only first so that a filesystem check can run safely against it, since checking a filesystem while processes are writing to it is how you turn a small problem into a large one.</p>
<p>Once that check passes, userspace remounts the same filesystem read-write in place, and the <code>errors=remount-ro</code> option you can see there is the reverse promise: if the kernel hits a filesystem error later, it drops back to read-only instead of writing to something it no longer trusts.</p>
<p>Now find the exact moment the kernel stopped being alone:</p>
<pre><code class="language-bash">journalctl -b -o short-monotonic | grep -m1 'systemd\[1\]'
</code></pre>
<pre><code class="language-text">[    3.152109] devils-dell systemd[1]: Inserted module 'autofs4'
</code></pre>
<p>3.15 seconds in, PID 1 logged its first line. That matches the 3.106 seconds <code>systemd-analyze</code> attributes to the kernel, and it's the handoff. From here the kernel does nothing on its own initiative. It answers system calls, and every decision about what runs next belongs to userspace.</p>
<h2 id="heading-what-the-initramfs-is-and-the-chicken-and-egg-problem-it-solves">What the <code>initramfs</code> is, and the Chicken-and-Egg Problem it Solves</h2>
<p>There's a step hidden inside that three seconds, and it's the part of Linux boot that confuses people most.</p>
<p>The kernel needs to mount your root filesystem. To do that, it needs a driver for your storage controller and a driver for the filesystem, and possibly code to assemble a RAID array, open an encrypted volume, or activate LVM. Those drivers live in modules. The modules live on the root filesystem. Which the kernel can't mount yet.</p>
<p>The way out is a small filesystem the bootloader loads into memory alongside the kernel, complete in itself. Look at it:</p>
<pre><code class="language-bash">ls -l /boot/initrd.img-$(uname -r)
lsinitramfs /boot/initrd.img-$(uname -r) | wc -l
lsinitramfs /boot/initrd.img-$(uname -r) | grep -c '\.ko'
</code></pre>
<p>Those paths and that tool are Debian and Ubuntu conventions. On Fedora the image is <code>/boot/initramfs-$(uname -r).img</code> and the tool is <code>lsinitrd</code>. On Arch it's usually <code>/boot/initramfs-linux.img</code>, read with <code>lsinitcpio</code>.</p>
<p>On this machine the image is 79,945,887 bytes, roughly 76 MB, holding 2,318 files of which 1,386 are kernel modules. It's a real, working, if minimal Linux system that exists to solve one problem: find and mount the actual root filesystem.</p>
<p>Once it succeeds, it does something unusual. It never exits. It swaps the real root into place, moves itself out of the way, and executes the real <code>/sbin/init</code> without ever starting a new process tree. PID 1 changes identity mid-flight and keeps its process ID.</p>
<p>If you use Arch or a minimal Fedora install, your initramfs may be a tenth of this size, because Ubuntu builds a generic one containing drivers for hardware you don't own so that the same image boots on any machine. That's a deliberate trade of size against portability, and <code>lsinitramfs</code> will show you what you're carrying.</p>
<h2 id="heading-pid-1-and-where-the-other-twelve-seconds-go">PID 1, and Where the Other Twelve Seconds Go</h2>
<p>The kernel finished at 3.1 seconds. The login screen appeared at 12.175 seconds of userspace. So what happened in between?</p>
<pre><code class="language-bash">systemctl get-default
systemctl list-unit-files --no-legend | wc -l
systemctl list-units --type=service --state=running --no-legend | wc -l
</code></pre>
<pre><code class="language-text">graphical.target
472
44
</code></pre>
<p>systemd's model is that you name a goal and it works out the order. The goal here is <code>graphical.target</code>, which wants <code>multi-user.target</code>, which wants a working network, filesystems, logging, and dozens of other things.</p>
<p>There are 472 unit files installed on this machine and 44 services actually running. systemd builds a dependency graph from those and starts everything it can in parallel, waiting only where a real dependency exists.</p>
<p>That parallelism is why boot analysis is harder than it looks. Dozens of things are happening at once, and the total isn't the sum of the parts.</p>
<h2 id="heading-why-systemd-analyze-blame-misleads-you-about-boot-time">Why <code>systemd-analyze blame</code> Misleads You About Boot Time</h2>
<p>The obvious next command is the wrong one, and the trap is worth walking into deliberately:</p>
<pre><code class="language-bash">systemd-analyze blame | head -5
</code></pre>
<pre><code class="language-text">3min 48.947s fstrim.service
     44.548s plocate-updatedb.service
     13.953s apt-daily.service
      4.875s docker.service
      4.106s NetworkManager-wait-online.service
</code></pre>
<p>Read that against the total. Userspace took 12.181 seconds. The top entry claims three minutes and forty-nine. Both numbers are correct, and the contradiction is the whole point.</p>
<p><code>blame</code> lists how long each unit took to start, for every unit systemd has started, whenever it started. It says nothing about whether the unit was on the path to your login screen. Check the top three:</p>
<pre><code class="language-bash">systemctl show fstrim.service -p TriggeredBy -p WantedBy
systemctl list-timers fstrim.timer
</code></pre>
<pre><code class="language-text">TriggeredBy=fstrim.timer
WantedBy=
NEXT                        LEFT        LAST                        PASSED
Mon 2026-09-14 00:59:36 IST 4 days left Mon 2026-09-07 00:33:35 IST 2 days ago
</code></pre>
<p><code>WantedBy</code> is empty, so nothing pulls it in at boot. It's triggered by a timer. It last ran two days ago and runs again in four.</p>
<p><code>plocate-updatedb.service</code> and <code>apt-daily.service</code> are the same shape, triggered by their own timers.</p>
<p>The top three entries in <code>blame</code>, nearly five minutes of apparent boot time, contributed exactly nothing to how long you waited for a login prompt.</p>
<p>The first entry that's genuinely on the boot path is <code>docker.service</code>, fourth in the list, at 4.875 seconds.</p>
<p>I'd rather you take the general lesson than the specific one. A measurement that reports on a superset of what you care about will mislead you in proportion to how much of that superset is irrelevant. <code>blame</code> isn't broken. It answers a different question than the one people ask it.</p>
<h2 id="heading-reading-the-critical-chain">Reading the Critical Chain</h2>
<p>The command that answers the actual question is this one:</p>
<pre><code class="language-bash">systemd-analyze critical-chain
</code></pre>
<pre><code class="language-text">graphical.target @12.175s
└─multi-user.target @12.175s
  └─docker.service @7.297s +4.875s
    └─network-online.target @7.295s
      └─NetworkManager-wait-online.service @3.188s +4.106s
        └─NetworkManager.service @3.151s +35ms
          └─network-pre.target @3.150s
            └─netfilter-persistent.service @1.377s +1.772s
              └─local-fs.target @1.374s
</code></pre>
<p>The <code>@</code> is when a unit became active. The <code>+</code> is how long it took. Now the twelve seconds make sense: <code>docker.service</code> at 4.875 and <code>NetworkManager-wait-online.service</code> at 4.106 account for nearly nine of them, and they're serialized because Docker wants a working network before it starts.</p>
<p><code>NetworkManager-wait-online</code> is the one to look at first on most desktops. It does what its name says, which is block until the network is actually up, and on a laptop associating with Wi-Fi that can be seconds of doing nothing. It exists so that services needing a network don't start before there is one. Whether you need that guarantee is a real question with a real answer, and it depends on what you run.</p>
<p>Two cautions about this output. It shows one chain, not every slow thing, so a unit that was slow but off the critical path never appears. And the <code>@</code> times aren't a causal sequence you can read top to bottom. My own output has a Docker network mount timestamped at 11 seconds nested underneath a target that completed at 650 milliseconds, which looks impossible until you realize the tree shows dependency edges and not a sequence of events. Read the <code>+</code> values for cost and the structure for ordering constraints, and don't read the nesting as a chronology.</p>
<h2 id="heading-why-your-linux-boot-time-will-be-different">Why Your Linux Boot Time Will Be Different</h2>
<p>Everything above is one boot on one machine, and the specific figures are worth less than the method. Before you compare yours to mine, know which parts move and why.</p>
<p>Firmware time varies more than anything else here, and it has almost nothing to do with Linux. A desktop with lots of RAM to train and a dozen USB devices to enumerate can spend fifteen seconds where this laptop spends six. If your firmware has a fast boot option, that option is what it sounds like: skipping enumeration steps, at the cost of not noticing hardware you plugged in.</p>
<p>Loader time is mostly your timeout, so it's mostly your decision. Kernel time moves with how much hardware you have and how much of the initramfs has to be unpacked and searched. This is why a distribution-generic initramfs like Ubuntu's costs more here than a host-specific one built for your machine alone. If your root filesystem is encrypted, some of what looks like kernel time is actually you typing a passphrase.</p>
<p>Userspace time is where your machine differs from mine most, because it reflects what you installed rather than what you own. Docker costs me nearly five seconds and would cost you nothing if you don't run it.</p>
<p>Run it a few times before drawing conclusions. Boot timing varies between runs on the same machine, and a single reading tells you less than you'd like.</p>
<h2 id="heading-the-login-screen-and-the-handoff-to-you">The Login Screen, and the Handoff to You</h2>
<p>The last step is the one you see:</p>
<pre><code class="language-bash">systemctl status display-manager --no-pager | head -1
loginctl show-session $(loginctl | awk 'NR==2{print $1}') -p Type -p Class
</code></pre>
<pre><code class="language-text">● lightdm.service - Light Display Manager
Type=x11
Class=user
</code></pre>
<p>On this machine, the display manager is LightDM, started as part of <code>graphical.target</code>. It opens a session on a virtual terminal, draws the greeter, and waits.</p>
<p>Behind it, systemd has already created a seat and a session slot for whoever logs in. When you type your password, the display manager authenticates through PAM, systemd assigns the session, and your desktop environment starts as a user process.</p>
<p>That <code>vt.handoff=7</code> from the kernel command line pays off here. It hands the virtual terminal to the graphical stack without the screen blanking and redrawing, which is the difference between a smooth boot and a flickering one.</p>
<p>From this point, the kernel is doing what it always does, which is answering system calls. If you want to follow what happens next, <a href="https://www.freecodecamp.org/news/how-a-system-call-actually-works-in-linux/">I wrote about that boundary in detail</a>.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>You can now account for your own boot, phase by phase, with numbers instead of guesses. On this laptop, 29.6 seconds breaks down as almost six seconds of firmware you can't control, eight and a half seconds of bootloader that's mostly a countdown you can delete, three seconds of kernel, and twelve seconds of userspace dominated by two services waiting on the network.</p>
<p>More usefully, you have a way to tell a real measurement from a plausible one. <code>systemd-analyze blame</code> looks authoritative and answers a question nobody asked. The critical chain answers the right question and still needs care, because its tree maps dependencies, not chronology.</p>
<p>A few directions from here. Set <code>GRUB_TIMEOUT=1</code> and regenerate the config, with <code>sudo update-grub</code> on Debian and Ubuntu or <code>sudo grub2-mkconfig -o /boot/grub2/grub.cfg</code> on Fedora, then reboot and watch five seconds vanish. Run <code>systemd-analyze plot &gt; boot.svg</code> and open it in a browser for the parallel view the text output flattens. Or look at whether <code>NetworkManager-wait-online.service</code> is earning its four seconds on your machine, which for most desktops it isn't.</p>
<h2 id="heading-epilogue">Epilogue</h2>
<p>The reason I went looking at any of this is that I'm building a Linux distribution with an Android-style permission model, where a program gets only the authority its manifest asks for rather than everything its user happens to have. That turns the boot sequence into a security question. Every process started before you log in runs with more authority than anything you launch afterward, and until I could name each one and say why it was there, I had no real way to argue about which of them deserved it.</p>
<p>You can find more of what I write at <a href="https://thechris.in">thechris.in</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How a System Call Actually Works in Linux ]]>
                </title>
                <description>
                    <![CDATA[ Here's a small C program. It calls clock_gettime() three times, then writes five bytes to standard output. #include <stdio.h> #include <time.h> #include <unistd.h> int main(void) {     struct timespe ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-a-system-call-actually-works-in-linux/</link>
                <guid isPermaLink="false">6a9f35bc726beec2fbecea20</guid>
                
                    <category>
                        <![CDATA[ Kernel ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Linux ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Systems Programming ]]>
                    </category>
                
                    <category>
                        <![CDATA[ operating system ]]>
                    </category>
                
                    <category>
                        <![CDATA[ linux kernel ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Chris Roy ]]>
                </dc:creator>
                <pubDate>Mon, 07 Sep 2026 22:07:56 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/9118bd52-fbfe-47e9-9f66-e25578632e91.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Here's a small C program. It calls <code>clock_gettime()</code> three times, then writes five bytes to standard output.</p>
<pre><code class="language-c">#include &lt;stdio.h&gt;
#include &lt;time.h&gt;
#include &lt;unistd.h&gt;

int main(void)
{
    struct timespec ts;

    for (int i = 0; i &lt; 3; i++)
        clock_gettime(CLOCK_MONOTONIC, &amp;ts);

    write(1, "done\n", 5);
    return 0;
}
</code></pre>
<p>Both of those look like system calls. Both of them ask the kernel for something your program can't get on its own: the current time, and access to a file descriptor.</p>
<p>Now run it under <code>strace</code>, which reports every system call a process makes:</p>
<pre><code class="language-bash">gcc -O0 -o mystery mystery.c
strace ./mystery 2&gt;&amp;1 | grep -c clock_gettime
</code></pre>
<p>The answer is <code>0</code>.</p>
<p>The <code>write()</code> shows up immediately. The three <code>clock_gettime()</code> calls don't appear at all. Same program, same libc, same machine, and one of them never reaches the kernel.</p>
<p>By the end of this article you'll know every step between your <code>write()</code> and the code that runs inside the kernel, why the return trip is stranger than the way in, and why <code>clock_gettime()</code> gets to skip the whole thing.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-what-you-need">What You Need</a></p>
</li>
<li><p><a href="#heading-what-a-system-call-looks-like-from-userspace">What a System Call Looks Like from Userspace</a></p>
</li>
<li><p><a href="#heading-the-crossing">The Crossing</a></p>
</li>
<li><p><a href="#heading-inside-the-kernel-finding-the-handler">Inside the Kernel: Finding the Handler</a></p>
</li>
<li><p><a href="#heading-the-return-trip-and-the-truth-about-errno">The Return Trip, and the Truth About errno</a></p>
</li>
<li><p><a href="#heading-the-system-call-that-never-happens">The System Call That Never Happens</a></p>
</li>
<li><p><a href="#heading-what-the-boundary-costs">What the Boundary Costs</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
<li><p><a href="#heading-epilogue">Epilogue</a></p>
</li>
</ul>
<h2 id="heading-what-you-need">What You Need</h2>
<p>You need an x86-64 machine running Linux, <code>gcc</code>, <code>strace</code>, and <code>objdump</code>. On Debian or Ubuntu that's <code>build-essential</code>, <code>strace</code>, and <code>binutils</code>. You also need to be comfortable reading C. You don't need to have written kernel code, and you won't build or install a kernel here.</p>
<p>Everything below runs on a normal user account, except for one optional tracing step that needs <code>sudo</code>.</p>
<p>Two warnings about scope. First, this article is about <strong>x86-64 only</strong>. ARM64 does the same job with different instructions and different register rules, and hedging every sentence for both would double the length and halve the clarity. Second, kernel internals move. I ran everything here on <strong>Linux 5.15 (Ubuntu 22.04, Intel Core i7-10750H)</strong>, and I'll flag the places where newer kernels differ. Check your own version with <code>uname -r</code>.</p>
<h2 id="heading-what-a-system-call-looks-like-from-userspace">What a System Call Looks Like from Userspace</h2>
<p>Let's start with a correction that matters: <code>write()</code> <strong>isn't a system call.</strong> It's an ordinary C function in your C library. That function makes a system call on your behalf, and the difference between those two things is where most confusion about the kernel begins.</p>
<p>You can prove it by cutting libc out and making the call yourself.</p>
<p>On x86-64, a system call has a fixed convention. You put the number of the call you want in <code>rax</code>, and its arguments in <code>rdi</code>, <code>rsi</code>, <code>rdx</code>, <code>r10</code>, <code>r8</code>, and <code>r9</code>, in that order. Then you execute a single instruction called <code>syscall</code>.</p>
<p>The numbers aren't something you memorise. They live in a header on your machine:</p>
<pre><code class="language-bash">grep -E "__NR_(write|getpid|clock_gettime) " /usr/include/x86_64-linux-gnu/asm/unistd_64.h
</code></pre>
<p>On this machine:</p>
<pre><code class="language-text">#define __NR_write 1
#define __NR_getpid 39
#define __NR_clock_gettime 228
</code></pre>
<p>So <code>write</code> is call number 1. Here's that call written by hand, with no libc wrapper involved:</p>
<pre><code class="language-c">static long raw_write(int fd, const void *buf, unsigned long count)
{
    long ret;

    __asm__ volatile (
        "syscall"
        : "=a" (ret)                   /* the result comes back in rax */
        : "a" (1L),        /* rax = 1, the syscall number for write */
          "D" ((long)fd),  /* rdi = first argument                  */
          "S" (buf),       /* rsi = second argument                 */
          "d" (count)      /* rdx = third argument                  */
        : "rcx", "r11", "memory"
    );

    return ret;
}
</code></pre>
<p>Compile and run it and your bytes turn up on standard output, with nothing from libc anywhere in the path.</p>
<p>Look at that last line, the clobber list. It tells the compiler <code>rcx</code> and <code>r11</code> are going to be destroyed. I didn't add that for safety. It's a fact about the hardware, and it quietly explains something odd about the convention above.</p>
<p>C functions on x86-64 pass their fourth argument in <code>rcx</code>. System calls pass theirs in <code>r10</code> instead. Every explanation that says "because that's the convention" stops one step too early. <strong>The real reason is that the</strong> <code>syscall</code> <strong>instruction overwrites</strong> <code>rcx</code> <strong>as part of doing its job.</strong> The kernel couldn't receive a fourth argument there even if it wanted to, so the ABI routed around its own hardware.</p>
<p>This is the first sign that this boundary isn't a function call wearing a costume. Different mechanism, different rules, and the hardware got there first.</p>
<p>You can see the instruction itself in your compiled binary:</p>
<pre><code class="language-bash">objdump -d --no-show-raw-insn raw_write | grep -B2 -A2 syscall
</code></pre>
<p>The instruction is right there:</p>
<pre><code class="language-text">    118b:	mov    -0x28(%rbp),%rdx
    118f:	syscall
    1191:	mov    %rax,-0x8(%rbp)
</code></pre>
<p>Three lines: load a register, execute one instruction, and store what came back. Everything else in this article happens between line two and line three.</p>
<h2 id="heading-the-crossing">The Crossing</h2>
<p>When the CPU executes <code>syscall</code>, it does something no ordinary jump can do: it changes the privilege level of the processor. Your code runs in what x86 calls ring 3. Kernel code runs in ring 0.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a783a81a29db580b40f1bc8/4c3a9e5f-db34-4bf7-8d41-6dc63fb09285.png" alt="Diagram showing a write system call traveling from userspace through the syscall instruction into the kernel, where the CPU loads the entry point from the LSTAR register, swaps to the kernel stack and builds a pt_regs structure, reaches the write handler, and returns with a value in rax" style="display: block;" width="600" height="400" loading="lazy">

<p>The instruction does three things, in order:</p>
<ol>
<li><p>It saves the address of the next instruction in your program into <code>rcx</code>. That's the return address, and it's why <code>rcx</code> gets clobbered.</p>
</li>
<li><p>It saves the CPU flags into <code>r11</code>.</p>
</li>
<li><p>It loads a new instruction pointer, code segment, and stack segment from three special CPU registers.</p>
</li>
</ol>
<p>That third step is the important one. The new instruction pointer doesn't come from your program. It comes from a machine-specific register called <code>LSTAR</code>, and <strong>the kernel wrote that register during boot</strong>.</p>
<p>That's the security property the whole design rests on. Userspace triggers the transition. Userspace doesn't get to pick where it lands. There's one door, the kernel installed it, and it opens on <code>entry_SYSCALL_64</code> in <code>arch/x86/entry/entry_64.S</code>.</p>
<p>Notice what the instruction does <em>not</em> do. It doesn't consult the interrupt descriptor table or push an exception frame. Older systems reached the kernel through <code>int 0x80</code>, a software interrupt with all of that machinery attached, and it was slow. The <code>syscall</code> instruction exists because this path was worth building dedicated hardware for.</p>
<h3 id="heading-becoming-the-kernel">Becoming the Kernel</h3>
<p>Arriving at <code>entry_SYSCALL_64</code> isn't the same as being ready to run kernel code. At the instant of arrival the CPU is in ring 0, but it's still using <strong>your</strong> stack and <strong>your</strong> register state. The kernel has to fix that before it can safely do anything.</p>
<p>Three things happen, and all three are the kernel establishing trust in a machine it is already running on:</p>
<h4 id="heading-1-swapgs">1. <code>swapgs</code></h4>
<p>The kernel keeps a per-CPU pointer in the <code>GS</code> register so it can find its own data structures. While you were running, <code>GS</code> held whatever your program put there. A single instruction, <code>swapgs</code>, exchanges it for the kernel's value. The kernel documentation is unusually blunt about this one, calling it fragile and warning that it must nest perfectly. Get it wrong in either direction and you have a very bad afternoon ahead of you.</p>
<h4 id="heading-2-the-stack-switch">2. The stack switch</h4>
<p>Your stack pointer is a value your program chose, so the kernel can't trust it. It stashes your <code>rsp</code> and switches to a kernel stack it allocated for this thread.</p>
<h4 id="heading-3-building-ptregs">3. Building <code>pt_regs</code></h4>
<p>The kernel then pushes your saved registers onto that new stack in a specific order, forming a C struct called <code>struct pt_regs</code>. <code>pt_regs</code> <strong>is your process, frozen.</strong> Every debugger that inspects a stopped process, every signal handler that modifies the context it returns to, and every system call handler reads its arguments out of that struct.</p>
<p>There may be a fourth step. If your CPU is vulnerable to Meltdown, the kernel also swaps page tables here, because on those chips the kernel's memory can't safely stay mapped while your code runs.</p>
<p>That swap isn't free. It's why system calls got measurably slower in 2018, and why some of the numbers later in this article would look different on a machine three years older.</p>
<p>You can check whether your machine pays that cost:</p>
<pre><code class="language-bash">cat /sys/devices/system/cpu/vulnerabilities/meltdown
</code></pre>
<p>The test machine here reports <code>Not affected</code>, because its generation of silicon has the fix in hardware. An older laptop will report <code>Mitigation: PTI</code>, and every system call it makes is doing extra work at exactly this point.</p>
<p>Look through the other files in that directory while you're there. Each one is a mitigation that this boundary may be paying for.</p>
<h2 id="heading-inside-the-kernel-finding-the-handler">Inside the Kernel: Finding the Handler</h2>
<p>The kernel is now running on its own stack with your registers safely captured. It calls a C function, <code>do_syscall_64</code>, and hands it two things: your <code>pt_regs</code>, and the system call number you left in <code>rax</code>.</p>
<p>Dispatch is short enough to describe completely. The kernel checks that your number is within range, clamps it, and jumps to the matching handler:</p>
<pre><code class="language-c">if (likely(nr &lt; NR_syscalls)) {
    nr = array_index_nospec(nr, NR_syscalls);
    regs-&gt;ax = x64_sys_call(regs, nr);
}
</code></pre>
<p>Two things in there need explaining.</p>
<p><code>array_index_nospec</code> is a Spectre mitigation. A plain bounds check isn't enough on a speculating CPU, because the processor may run ahead and touch memory past the end of the table before the check resolves. This helper forces the index to be clamped in a way speculation can't skip.</p>
<p><code>x64_sys_call</code> is where a lot of older explanations are now wrong, including some still near the top of search results. They'll tell you the kernel indexes an array of function pointers called <code>sys_call_table</code>. <strong>That was true for many years and is no longer how dispatch works.</strong> Since kernel 6.9, <code>x64_sys_call</code> is a generated <code>switch</code> statement of direct calls.</p>
<p>The reason is a chain of consequences. Spectre mitigations made indirect calls through function pointers expensive, because each one has to go through a retpoline.</p>
<p>A <code>switch</code> of direct calls avoids that cost entirely. The table still exists, because tracing tools use it, but the hot path no longer reads it. On my 5.15 kernel the older table-based dispatch is still in place, which is exactly why naming your kernel version in an article like this one matters.</p>
<h3 id="heading-where-the-handler-comes-from">Where the Handler Comes From</h3>
<p>The handler for <code>write</code> is named <code>__x64_sys_write</code>, and you won't find that name written anywhere in the kernel source. It's generated by a macro:</p>
<pre><code class="language-c">SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf, size_t, count)
</code></pre>
<p><code>SYSCALL_DEFINE3</code> means "a system call taking three arguments". The macro expands into two functions: the real implementation, and a thin wrapper named <code>__x64_sys_write</code> that takes a single <code>struct pt_regs *</code> and pulls the arguments out of it.</p>
<p>That indirection is deliberate. Rather than trusting whatever userspace happened to leave in the argument registers, the kernel unpacks exactly the values it expects from the frozen struct it built itself. It's the same defensive instinct as <code>array_index_nospec</code>, applied to the shape of the function call.</p>
<p>You don't have to take any of this on faith. <code>ftrace</code>, the kernel's built-in tracer, will show you the handler running.</p>
<p>This needs a root shell rather than <code>sudo</code> on each line, because the filter that keeps the output readable refers to the shell's own process ID:</p>
<pre><code class="language-bash">sudo -i
cd /sys/kernel/tracing

echo 0 &gt; tracing_on
echo $$ &gt; set_ftrace_pid              # trace only this shell
echo function_graph &gt; current_tracer
echo __x64_sys_write &gt; set_graph_function

echo 1 &gt; tracing_on
echo "trigger a write" &gt; /dev/null    # the call we want to catch
echo 0 &gt; tracing_on

head -40 trace
</code></pre>
<p>Without that <code>set_ftrace_pid</code> line, you'll trace every write on the machine, which on a running desktop is far too much output to read.</p>
<p>Here's the result on the test system, lightly trimmed:</p>
<pre><code class="language-text"> 9)               |  __x64_sys_write() {
 9)               |    ksys_write() {
 9)               |      __fdget_pos() {
 9)   0.124 us    |        __fget_light();
 9)   0.363 us    |      }
 9)               |      vfs_write() {
 9)               |        rw_verify_area() {
 9)               |          security_file_permission() {
 9)               |            apparmor_file_permission() {
 9)   0.264 us    |              aa_file_perm();
 9)   0.457 us    |            }
 9)   0.644 us    |          }
 9)   0.857 us    |        }
 9)   0.083 us    |        write_null();
 9)               |        __fsnotify_parent() {
 9)   0.107 us    |          fsnotify();
 9)   1.383 us    |        }
 9)   2.813 us    |      }
 9)   3.449 us    |    }
 9)   3.720 us    |  }
</code></pre>
<p>Read that from the outside in and you have the whole descent in twenty lines.</p>
<p><code>__x64_sys_write</code> is the generated wrapper. It calls <code>ksys_write</code>, the real implementation. That looks up your file descriptor with <code>__fdget_pos</code>, then hands off to <code>vfs_write</code>, the virtual filesystem layer, which is where the kernel stops caring what kind of thing you're writing to.</p>
<p>Then <code>security_file_permission</code> calls into AppArmor, because this machine runs Ubuntu. On a SELinux system something else sits there. Either way it's a security module deciding whether you're allowed to do this. On every write. Every one.</p>
<p><code>write_null</code> is the payoff, and it's there by accident: the command above wrote to <code>/dev/null</code>, so that's the actual driver, the one whose whole job is throwing your bytes away. Point the same write at a file on disk and a filesystem function shows up in that slot instead. Nothing above it moves.</p>
<p>The whole thing took 3.7 microseconds, and the timings on the right tell you where it went.</p>
<p>When you're done, put the tracer back:</p>
<pre><code class="language-bash">echo nop &gt; current_tracer
echo &gt; set_graph_function
echo &gt; set_ftrace_pid
</code></pre>
<p>If <code>/sys/kernel/tracing</code> doesn't exist on your system, try <code>/sys/kernel/debug/tracing</code> instead.</p>
<h2 id="heading-the-return-trip-and-the-truth-about-errno">The Return Trip, and the Truth About <code>errno</code></h2>
<p>The handler finishes and returns a number. That number goes into <code>rax</code>, and <code>rax</code> is the only thing your program gets back.</p>
<p>Which raises a question that is rarely asked directly: if the kernel can only return one value, how does it report <em>what went wrong</em> as well as <em>that</em> something went wrong?</p>
<p>The answer is that it doesn't have a separate channel. <strong>The kernel returns errors as small negative numbers in the same register as the result.</strong> A successful <code>write</code> of 24 bytes returns 24. A <code>write</code> to a closed descriptor returns -9, because <code>EBADF</code> is error number 9.</p>
<p>Now put that together with the fact that <code>errno</code> exists, and something doesn't add up. <code>errno</code> is a variable in your process. The kernel doesn't write to your variables.</p>
<p>Here's the test. Set <code>errno</code> to zero, make a raw system call that's guaranteed to fail, and look at both values:</p>
<pre><code class="language-c">#include &lt;stdio.h&gt;         /* fprintf, stderr */
#include &lt;errno.h&gt;         /* errno */

/* raw_write() is the function from the previous section */

errno = 0;

long ok  = raw_write(1, "written via raw syscall\n", 24);
long bad = raw_write(999, "x", 1);          /* not an open descriptor */

fprintf(stderr, "ok = %ld\n",  ok);
fprintf(stderr, "bad = %ld\n", bad);
fprintf(stderr, "errno = %d\n", errno);
</code></pre>
<p>Running it:</p>
<pre><code class="language-text">written via raw syscall
ok = 24
bad = -9
errno = 0
</code></pre>
<p>There it is. The kernel returned <code>-9</code>, and <code>errno</code> never moved.</p>
<p><code>errno</code> <strong>is a libc invention.</strong> When you call the normal <code>write()</code>, the wrapper checks whether the return value is a small negative number. If it is, it negates it, stores the result in <code>errno</code>, and returns <code>-1</code> to you. The <code>-1</code>-and-check-<code>errno</code> pattern every C programmer learns is a convention built entirely in userspace, on top of a kernel interface that works a completely different way.</p>
<p>Once you've seen this, a familiar bug class makes more sense. <code>errno</code> is only meaningful immediately after a failed call, because it's just a variable that the last wrapper to fail happened to write to.</p>
<h3 id="heading-two-ways-out">Two Ways Out</h3>
<p>Getting back to userspace has a fast path and a slow path.</p>
<p>The fast path is <code>sysret</code>, the mirror of <code>syscall</code>: it restores your instruction pointer from <code>rcx</code> and your flags from <code>r11</code> and drops back to ring 3 in a few cycles.</p>
<p>The slow path is <code>iret</code>, the general-purpose return-from-interrupt instruction. It's significantly slower, and the kernel uses it when <code>sysret</code> can't be trusted. The entry code's own comments explain why: <code>sysret</code> has trouble with non-canonical addresses due to bugs in both AMD and Intel CPUs, so whenever something might have changed your saved state, the kernel forces the safe path. A debugger reaching in through <code>ptrace</code> and changing your registers is the usual culprit.</p>
<p>Before either instruction runs, the kernel does the housekeeping it deferred. It checks for pending signals and delivers them. It checks whether the scheduler wants the CPU back, and if so, your process stops here and something else runs.</p>
<p>Which means a system call isn't only a request for service. It's one of the main places your process can simply stop running. You asked to write five bytes. On the way back the kernel gets to reconsider everything about you, including whether you should continue at all.</p>
<h2 id="heading-the-system-call-that-never-happens">The System Call That Never Happens</h2>
<p>Now back to the mystery from the opening.</p>
<p>Look at your own process's memory map:</p>
<pre><code class="language-bash">cat /proc/self/maps | tail -4
</code></pre>
<p>which ends with:</p>
<pre><code class="language-text">7fff32f97000-7fff32f9b000 r--p  [vvar]
7fff32f9b000-7fff32f9d000 r-xp  [vdso]
</code></pre>
<p>Two regions you never asked for. Neither came from your program or your libraries. The kernel put them there, in every process on the system.</p>
<p><code>[vdso]</code> stands for virtual dynamic shared object. It's a small, complete shared library (real ELF, with a symbol table) that the kernel maps into every address space. And because the kernel tells each process where it put it, you can dump your own copy and take it apart:</p>
<pre><code class="language-c">#include &lt;stdio.h&gt;
#include &lt;sys/auxv.h&gt;      /* getauxval, AT_SYSINFO_EHDR */
#include &lt;unistd.h&gt;        /* getpagesize */

int main(void)
{
    void  *vdso = (void *)getauxval(AT_SYSINFO_EHDR);   /* the kernel tells us where */
    size_t len  = 2 * getpagesize();                    /* the mapping is two pages  */

    FILE *f = fopen("vdso.so", "wb");
    fwrite(vdso, 1, len, f);
    fclose(f);

    printf("vDSO was mapped at %p\n", vdso);
    return 0;
}
</code></pre>
<p><code>AT_SYSINFO_EHDR</code> lives in <code>&lt;sys/auxv.h&gt;</code>. Leave that header out and you don't get a polite warning about it: the build stops with <code>AT_SYSINFO_EHDR undeclared</code>.</p>
<p>Run that, then read its symbol table like any other library:</p>
<pre><code class="language-bash">./dump_vdso &amp;&amp; objdump -T vdso.so | grep __vdso
</code></pre>
<p>and out comes:</p>
<pre><code class="language-text">__vdso_gettimeofday
__vdso_clock_gettime
__vdso_clock_getres
__vdso_time
__vdso_getcpu
</code></pre>
<p>There's the answer. <code>clock_gettime</code> is in that list.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a783a81a29db580b40f1bc8/8f16ecfa-995c-43b1-8257-4033cc485998.png" alt="Diagram comparing two calls: getpid crossing into the kernel through the syscall instruction and taking about 100 nanoseconds, and clock_gettime staying in userspace by calling into the vDSO, reading a shared read-only page, and taking about 16 nanoseconds" style="display: block;" width="600" height="400" loading="lazy">

<p>When you call <code>clock_gettime()</code>, libc calls into the vDSO. That code is written by kernel developers and shipped with the kernel, but it <strong>executes in ring 3, as part of your process</strong>. It reads the current time out of the <code>[vvar]</code> page (a read-only page the kernel keeps updated) and returns.</p>
<p>There's no privilege change, <code>syscall</code> instruction, entry point, stack switch, or <code>pt_regs</code>. And that means there's nothing for <code>strace</code> to see, because <code>strace</code> works by watching the boundary, and this call never goes near it.</p>
<p>That's the whole trick. The kernel took a handful of operations that are called constantly, need no privileges to <em>read</em>, and only ever return information the kernel is willing to publish – and it published them.</p>
<p>That last constraint explains why the list is so short. <code>write()</code> can never work this way, because it has to change state that belongs to the kernel. Reading the clock does not. So the clock, the time of day, and the current CPU number moved out to where the caller already is.</p>
<h2 id="heading-what-the-boundary-costs">What the Boundary Costs</h2>
<p>Everything above is mechanism. Here's the price, measured.</p>
<p>The benchmark compares a call that definitely traps against one that definitely does not. For the first, use <code>syscall(SYS_getpid)</code>. Going through the thin <code>syscall()</code> wrapper guarantees a real crossing:</p>
<pre><code class="language-c">/* Excerpt. Needs &lt;unistd.h&gt;, &lt;sys/syscall.h&gt; and &lt;time.h&gt;, plus a now()
   helper returning seconds as a double, and ITERATIONS defined above. */

double a = now();
for (long i = 0; i &lt; ITERATIONS; i++)
    sink += syscall(SYS_getpid);

double b = now();
for (long i = 0; i &lt; ITERATIONS; i++)
    clock_gettime(CLOCK_MONOTONIC, &amp;ts);
</code></pre>
<p>On the test machine, two million iterations of each:</p>
<pre><code class="language-text">real system call (getpid):   106.9 ns/call
vDSO call (clock_gettime):    17.2 ns/call
ratio:                          6.2x
</code></pre>
<p>Roughly six times, and <code>getpid</code> is about as cheap as a system call gets. It reads one field and returns. Which means almost none of that 107 nanoseconds is the work. It's the privilege change, <code>swapgs</code>, the stack switch, <code>pt_regs</code> going up and coming back down, plus whatever mitigations your particular CPU insists on along the way.</p>
<p>Now the caveat, because it matters more than the number.</p>
<p><strong>That 107 nanoseconds is close to a best case.</strong> Check what this machine reported earlier: <code>Not affected</code> for Meltdown, so it never does the page-table swap. Its Spectre mitigation is <code>Enhanced IBRS</code>, which is handled in silicon rather than by retpolines in software. This CPU is skipping two of the most expensive things a crossing can involve.</p>
<p>So run the benchmark yourself, and read your own mitigation files alongside it:</p>
<pre><code class="language-bash">grep . /sys/devices/system/cpu/vulnerabilities/*
</code></pre>
<p>If yours says <code>Mitigation: PTI</code>, your crossings are doing strictly more work than the ones measured here, and your number should be higher. Older silicon can be dramatically worse.</p>
<p>Treat the ratio as the durable result and the absolute number as one reading from one machine. The figure moves with your CPU, your kernel, and whichever mitigations you happen to be carrying. Run it a few times while you're there. The spread between runs on this laptop was about fifteen percent, which tells you roughly how much to trust any single number, including mine.</p>
<p>One more result from the same run corrects a widely repeated claim. <code>getpid()</code> through normal libc costs the same as the raw <code>syscall(SYS_getpid)</code>. glibc used to cache the process ID to avoid the trip, and stopped years ago, because keeping the cache correct across <code>fork</code> and namespace changes was worse than paying the hundred nanoseconds.</p>
<p>Six times sounds abstract until you attach it to something. A program making a million small <code>read()</code> calls spends about a tenth of a second on nothing but crossings. This is the pressure behind a lot of modern kernel interface design: <code>io_uring</code> exists so that submitting a thousand operations can cost one crossing instead of a thousand. Batching syscalls, buffering writes, and using <code>sendfile()</code> instead of a read-write loop are all the same optimisation: not doing less work, just crossing the boundary fewer times.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>You can now follow a system call the whole way. You've seen the <code>syscall</code> instruction in your own binary, watched a bad file descriptor come back as <code>-9</code> while <code>errno</code> stayed at zero, pulled the vDSO out of your own address space and read its symbol table, and measured what the crossing costs on your own CPU.</p>
<p>More usefully, you have a mental model that keeps paying out. When you read that <code>io_uring</code> reduces syscall overhead, you know exactly what overhead means. When a profile shows time in <code>entry_SYSCALL_64</code>, you know what that function does. When <code>strace</code> shows nothing, you know to check the vDSO before doubting the tool.</p>
<p>There are a few directions to go from here. Run the <code>ftrace</code> recipe and follow <code>__x64_sys_write</code> down into the filesystem layer. Read <code>arch/x86/entry/entry_64.S</code>: it's heavily commented and much more approachable than its reputation suggests. Or check <code>/sys/devices/system/cpu/vulnerabilities/</code> on an older machine and work out what each mitigation is costing you at this boundary.</p>
<h2 id="heading-epilogue">Epilogue</h2>
<p>I'm currently experimenting with an OS design on top of the Linux kernel that would bring an Android-style permissions and capabilities model to a desktop OS while trying to be 100% compatible with the Debian ecosystem. This has led to some really interesting research lately. This article is a product of that research.</p>
<p>I'll be writing more about the Linux kernel before I move on to formal verification, as in the DO-178C and DO-333 world where avionics software has to qualify the tools that check it. Usually that means <a href="https://www.pm.inf.ethz.ch/research/viper.html">Viper</a>, <a href="https://why3.org/">Why3</a>, <a href="https://www.microsoft.com/en-us/research/project/z3-3/">Z3</a> and friends.</p>
<p>In the meantime, I also write about systems that have to survive contact with reality at <a href="https://thechris.in">thechris.in</a>, including a companion piece to this one, on what it means to build on an abstraction whose cost you can measure but never see.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Host Odoo: Self-Hosted vs Managed Hosting ]]>
                </title>
                <description>
                    <![CDATA[ Odoo is an open-source enterprise resource planning (ERP) platform that helps businesses manage operations such as sales, customer relationship management (CRM), inventory, accounting, human resources ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-host-odoo/</link>
                <guid isPermaLink="false">6a888912ca3910a01e7c2f66</guid>
                
                    <category>
                        <![CDATA[ Odoo ]]>
                    </category>
                
                    <category>
                        <![CDATA[ self-hosted ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Devops ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Linux ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Open Source ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Abdul Talha ]]>
                </dc:creator>
                <pubDate>Fri, 21 Aug 2026 17:21:22 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/30766bac-af3a-4c24-a544-75846002ce99.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Odoo is an open-source enterprise resource planning (ERP) platform that helps businesses manage operations such as sales, customer relationship management (CRM), inventory, accounting, human resources, and manufacturing from a single application.</p>
<p>You can deploy it in different hosting environments, which gives you the flexibility to choose a deployment model that fits your needs.</p>
<p>Choosing the right hosting option is an important part of any Odoo deployment. It affects factors such as performance, security, maintenance, scalability, and long-term operational costs.</p>
<p>Your team can either self-host Odoo on your own infrastructure or use a managed hosting provider to handle server management. Each approach offers different levels of control, flexibility, and operational responsibility.</p>
<p>In this article, you'll learn about the different ways to host Odoo, including how to set up a basic self-hosted deployment. You'll also compare self-hosted and managed hosting and explore the advantages and limitations of each approach.</p>
<p>By the end, you'll have a better understanding of which hosting model best fits your business and technical requirements.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6729b04417afd6915f5c2e3e/e1de2a5c-7186-4aff-a8e3-84bbe6eb1ea2.png" alt="e1de2a5c-7186-4aff-a8e3-84bbe6eb1ea2" style="display: block;" width="1920" height="1280" loading="lazy">

<h3 id="heading-what-well-cover">What We'll Cover:</h3>
<ul>
<li><p><a href="#heading-what-are-your-odoo-hosting-options">What Are Your Odoo Hosting Options?</a></p>
</li>
<li><p><a href="#heading-self-hosted-odoo">Self-Hosted Odoo</a></p>
</li>
<li><p><a href="#heading-managed-odoo-hosting">Managed Odoo Hosting</a></p>
</li>
<li><p><a href="#heading-self-hosted-vs-managed-hosting">Self-Hosted vs Managed Hosting</a></p>
</li>
<li><p><a href="#heading-how-to-choose-the-right-option">How to Choose the Right Option</a></p>
</li>
<li><p><a href="#heading-key-factors-to-consider-before-choosing">Key Factors to Consider Before Choosing</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-what-are-your-odoo-hosting-options">What Are Your Odoo Hosting Options?</h2>
<p>Odoo can be hosted in different ways depending on your organization's needs. The main difference between these options is who manages the infrastructure and day-to-day maintenance. In most cases, businesses choose between two hosting models:</p>
<ol>
<li><p><strong>Self-Hosted Odoo:</strong> With a self-hosted deployment, you install and manage Odoo on infrastructure that you control, such as a virtual private server (VPS), dedicated server, cloud virtual machine, or on-premises server. This approach gives you greater control over the environment, but your team is also responsible for maintaining and securing it.</p>
</li>
<li><p><strong>Managed Odoo Hosting:</strong> With managed hosting, a hosting provider manages the infrastructure and handles routine maintenance tasks. This allows your team to focus on using Odoo for business operations instead of managing servers.</p>
</li>
</ol>
<p>The following sections examine both hosting models in more detail, including their benefits, limitations, and ideal use cases. You'll also learn how to set up a basic self-hosted Odoo deployment and what to consider before choosing a hosting option.</p>
<h2 id="heading-self-hosted-odoo">Self-Hosted Odoo</h2>
<p>Self-hosting Odoo means deploying and managing the application on infrastructure that you control, such as a virtual private server (VPS), dedicated server, cloud virtual machine, or an on-premises server.</p>
<p>With this approach, your organization is responsible for installing, configuring, maintaining, and securing both Odoo and the infrastructure.</p>
<h3 id="heading-benefits-of-self-hosting">Benefits of Self-Hosting</h3>
<p>Self-hosting gives you greater control and flexibility over your deployment. Some of the key benefits include:</p>
<ul>
<li><p>Complete control over the hosting environment.</p>
</li>
<li><p>Freedom to choose your operating system, database configuration, and hosting provider.</p>
</li>
<li><p>Support for custom modules, integrations, and server configurations.</p>
</li>
<li><p>Flexibility to optimize performance based on your workload.</p>
</li>
<li><p>Greater control over scaling and infrastructure resources.</p>
</li>
</ul>
<h3 id="heading-challenges-of-self-hosting">Challenges of Self-Hosting</h3>
<p>Along with greater control comes additional responsibility. When you self-host Odoo, you are responsible for:</p>
<ul>
<li><p>Installing software updates and security patches.</p>
</li>
<li><p>Managing backups and disaster recovery.</p>
</li>
<li><p>Monitoring server performance and application availability.</p>
</li>
<li><p>Troubleshooting infrastructure and application issues.</p>
</li>
<li><p>Securing the server against potential threats.</p>
</li>
</ul>
<p>Organizations should ensure they have the necessary technical expertise before choosing this deployment model.</p>
<h3 id="heading-who-should-choose-self-hosted-odoo">Who Should Choose Self-Hosted Odoo?</h3>
<p>Self-hosted Odoo is a good choice for:</p>
<ul>
<li><p>Developers and DevOps teams.</p>
</li>
<li><p>Organizations with in-house IT administrators.</p>
</li>
<li><p>Businesses that require extensive customization.</p>
</li>
<li><p>Teams that need complete control over their infrastructure.</p>
</li>
<li><p>Organizations with specific security or compliance requirements.</p>
</li>
</ul>
<h3 id="heading-how-to-self-host-odoo">How to Self Host Odoo</h3>
<p>The following steps show how to deploy Odoo using Docker Compose, PostgreSQL, and Traefik. Traefik acts as the reverse proxy and handles HTTPS certificates for your domain.</p>
<h4 id="heading-prerequisites">Prerequisites</h4>
<ul>
<li><p>Linux server with 2 vCPU and 4 GB RAM.</p>
</li>
<li><p>Docker and Docker Compose installed.</p>
</li>
<li><p>Domain name with an A record pointing to the server.</p>
</li>
<li><p>Inbound TCP traffic allowed on ports <strong>80</strong> and <strong>443</strong>.</p>
</li>
</ul>
<h4 id="heading-prepare-the-project-directory">Prepare the Project Directory</h4>
<p>Create a directory for the Odoo deployment:</p>
<pre><code class="language-shell">mkdir ~/odoo
</code></pre>
<p>Navigate to the project directory:</p>
<pre><code class="language-shell">cd ~/odoo
</code></pre>
<p>Create directories for persistent Odoo data, PostgreSQL data, custom addons, and Let's Encrypt certificates:</p>
<pre><code class="language-shell">mkdir -p odoo-data postgres-data addons letsencrypt
</code></pre>
<p>Set the ownership of the Odoo data and addons directories to the user used by the Odoo container:</p>
<pre><code class="language-shell">sudo chown -R 100:101 ~/odoo/odoo-data ~/odoo/addons
</code></pre>
<p>Create the environment file:</p>
<pre><code class="language-shell">nano .env
</code></pre>
<p>Add the following configuration. Replace the domain, email address, and passwords with your own values.</p>
<pre><code class="language-plaintext">DOMAIN=odoo.example.com 
LETSENCRYPT_EMAIL=admin@example.com 

POSTGRES_DB=postgres 
POSTGRES_USER=odoo 
POSTGRES_PASSWORD=STRONG_DATABASE_PASSWORD 

ODOO_DB_HOST=db 
ODOO_DB_PORT=5432 
ODOO_DB_USER=odoo 
ODOO_DB_PASSWORD=STRONG_DATABASE_PASSWORD

ODOO_ADMIN_PASSWORD=STRONG_ADMIN_PASSWORD
</code></pre>
<p>Save and close the file.</p>
<h4 id="heading-create-the-docker-compose-configuration">Create the Docker Compose Configuration</h4>
<p>Create the Docker Compose file like this:</p>
<pre><code class="language-shell">nano docker-compose.yml
</code></pre>
<p>Add the following configuration:</p>
<pre><code class="language-yaml">services:
  traefik:
    image: traefik:v3.7
    container_name: traefik
    command:
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--entrypoints.web.http.redirections.entrypoint.to=websecure"
      - "--entrypoints.web.http.redirections.entrypoint.scheme=https"
      - "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
      - "--certificatesresolvers.letsencrypt.acme.email=${LETSENCRYPT_EMAIL}"
      - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./letsencrypt:/letsencrypt
    restart: unless-stopped

  db:
    image: postgres:15
    container_name: odoo-db
    environment:
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - ./postgres-data:/var/lib/postgresql/data
    restart: unless-stopped

  odoo:
    image: odoo:19.0
    container_name: odoo
    depends_on:
      - db
    environment:
      HOST: ${ODOO_DB_HOST}
      PORT: ${ODOO_DB_PORT}
      USER: ${ODOO_DB_USER}
      PASSWORD: ${ODOO_DB_PASSWORD}
    command:
      - "--admin-passwd=${ODOO_ADMIN_PASSWORD}"
    volumes:
      - ./odoo-data:/var/lib/odoo
      - ./addons:/mnt/extra-addons
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.odoo.rule=Host(`${DOMAIN}`)"
      - "traefik.http.routers.odoo.entrypoints=websecure"
      - "traefik.http.routers.odoo.tls=true"
      - "traefik.http.routers.odoo.tls.certresolver=letsencrypt"
      - "traefik.http.services.odoo.loadbalancer.server.port=8069"
    restart: unless-stopped
</code></pre>
<p>Start the services in detached mode:</p>
<pre><code class="language-shell">docker compose up -d
</code></pre>
<p>Verify that all services are running:</p>
<pre><code class="language-shell">docker compose ps
</code></pre>
<p>Check the Odoo logs:</p>
<pre><code class="language-shell">docker compose logs odoo --tail=50
</code></pre>
<h4 id="heading-configure-postgresql">Configure PostgreSQL</h4>
<p>Check the privileges of the <code>odoo</code> PostgreSQL role:</p>
<pre><code class="language-shell">docker exec -it odoo-db psql -U odoo -d postgres -c "\du"
</code></pre>
<p>Confirm that the <code>odoo</code> role has the <code>Create DB</code> attribute.</p>
<p>If the attribute is missing, grant it using the PostgreSQL administrator account.</p>
<pre><code class="language-shell">docker exec -it odoo-db psql -U postgres -d postgres -c "ALTER ROLE odoo CREATEDB;"
</code></pre>
<p>Verify the privilege again:</p>
<pre><code class="language-shell">docker exec -it odoo-db psql -U odoo -d postgres -c "\du"
</code></pre>
<h4 id="heading-access-and-configure-odoo">Access and Configure Odoo</h4>
<p>Open <code>https://odoo.example.com</code> in your browser, replacing the domain with your own.</p>
<p>Then click <strong>Create Database</strong>.</p>
<p>Enter the master password from <code>ODOO_ADMIN_PASSWORD</code> in your <code>.env</code> file. Enter a database name, such as <code>odoo</code>. And enter the administrator email and password.</p>
<p>Select your language and country and click <strong>Create Database</strong>. Odoo creates the database and opens the dashboard. Log in with your administrator credentials.</p>
<h2 id="heading-managed-odoo-hosting">Managed Odoo Hosting</h2>
<p>Managed Odoo hosting is a deployment model where a hosting provider manages the underlying infrastructure and routine maintenance tasks. Instead of provisioning and maintaining servers yourself, you rely on the provider to manage the hosting environment, allowing your team to focus on using Odoo for day-to-day business operations.</p>
<p>Managed Odoo hosting is available through Odoo itself using <a href="http://Odoo.sh">Odoo.sh</a>, as well as through third-party providers such as <a href="https://cloudpepper.io/">CloudPepper</a> and <a href="https://www.rosehosting.com/">RoseHosting</a>. The level of server access, customization, maintenance, and infrastructure management varies between providers, so it's important to review what each provider includes before choosing a service.</p>
<h3 id="heading-benefits-of-managed-hosting">Benefits of Managed Hosting</h3>
<p>Managed hosting simplifies Odoo deployment by reducing the effort required to maintain the underlying infrastructure. Depending on the provider and plan, common benefits may include:</p>
<ul>
<li><p>Faster deployment without extensive server setup.</p>
</li>
<li><p>Assistance with software updates and security maintenance.</p>
</li>
<li><p>Automated backups and disaster recovery options.</p>
</li>
<li><p>Infrastructure monitoring and performance management.</p>
</li>
<li><p>Technical support for infrastructure-related issues.</p>
</li>
<li><p>Easier scaling as business requirements grow.</p>
</li>
</ul>
<h3 id="heading-limitations-of-managed-hosting">Limitations of Managed Hosting</h3>
<p>While managed hosting offers convenience, it also comes with certain trade-offs. Organizations should consider the following:</p>
<ul>
<li><p>Limited control over the underlying server environment.</p>
</li>
<li><p>Fewer customization options compared to self-hosting, depending on the provider.</p>
</li>
<li><p>Provider-specific restrictions on server access or configurations.</p>
</li>
<li><p>Recurring hosting or subscription costs.</p>
</li>
<li><p>Dependence on the provider for certain maintenance and infrastructure tasks.</p>
</li>
</ul>
<h3 id="heading-who-should-choose-managed-odoo-hosting">Who Should Choose Managed Odoo Hosting?</h3>
<p>Managed Odoo hosting can be a good choice for:</p>
<ul>
<li><p>Small and medium-sized businesses.</p>
</li>
<li><p>Organizations without dedicated IT or DevOps teams.</p>
</li>
<li><p>Teams that want to reduce the effort of managing infrastructure.</p>
</li>
<li><p>Businesses looking for a faster and simpler deployment.</p>
</li>
<li><p>Organizations that prefer a low-maintenance hosting solution.</p>
</li>
</ul>
<h2 id="heading-self-hosted-vs-managed-hosting">Self-Hosted vs Managed Hosting</h2>
<p>Both self-hosted and managed hosting allow you to deploy and run Odoo. The right choice depends on your organization's technical expertise, operational requirements, customization needs, and budget. The following table compares the key differences between the two hosting models.</p>
<table>
<thead>
<tr>
<th>Feature</th>
<th>Self-Hosted Odoo</th>
<th>Managed Odoo Hosting</th>
</tr>
</thead>
<tbody><tr>
<td>Setup</td>
<td>Install and configure Odoo yourself</td>
<td>Provider handles deployment and initial setup</td>
</tr>
<tr>
<td>Infrastructure Management</td>
<td>Managed by your organization</td>
<td>Managed by the hosting provider</td>
</tr>
<tr>
<td>Server Control</td>
<td>Full control over the server environment</td>
<td>Limited server-level control</td>
</tr>
<tr>
<td>Customization</td>
<td>Extensive customization and configuration options</td>
<td>May be limited by provider policies</td>
</tr>
<tr>
<td>Updates</td>
<td>Managed internally</td>
<td>Typically handled or supported by the provider</td>
</tr>
<tr>
<td>Security</td>
<td>Organization manages security patches and server hardening</td>
<td>Provider manages infrastructure security and may handle security updates</td>
</tr>
<tr>
<td>Backups</td>
<td>Configured and maintained by your organization</td>
<td>Often automated, depending on the provider</td>
</tr>
<tr>
<td>Monitoring</td>
<td>Managed internally</td>
<td>Often provided by the hosting provider</td>
</tr>
<tr>
<td>Technical Expertise</td>
<td>Requires Linux and server administration skills</td>
<td>Less infrastructure expertise required</td>
</tr>
<tr>
<td>Scalability</td>
<td>Organization manages infrastructure scaling</td>
<td>Often easier to scale through the provider</td>
</tr>
<tr>
<td>Support</td>
<td>Internal IT team or community support</td>
<td>Technical support provided by the hosting provider</td>
</tr>
<tr>
<td>Cost</td>
<td>Infrastructure costs plus maintenance effort</td>
<td>Recurring hosting fees with reduced maintenance overhead</td>
</tr>
</tbody></table>
<p>Self-hosting is a good choice for organizations that need greater control and customization, while managed hosting is better suited for teams that want to reduce the effort of managing infrastructure and focus on business operations. The right option depends on your technical expertise, operational requirements, customization needs, and long-term business goals.</p>
<h2 id="heading-how-to-choose-the-right-option">How to Choose the Right Option</h2>
<p>Choosing between self-hosted and managed Odoo hosting depends on your organization's technical expertise, business requirements, budget, and how much time your team can dedicate to managing infrastructure.</p>
<h3 id="heading-choose-self-hosted-odoo-if">Choose Self-Hosted Odoo If</h3>
<p>Self-hosting may be a better fit if you:</p>
<ul>
<li><p>Have an in-house IT or DevOps team with Linux and cloud administration experience.</p>
</li>
<li><p>Need full control over the server environment.</p>
</li>
<li><p>Require extensive customization or third-party integrations.</p>
</li>
<li><p>Have specific security, compliance, or performance requirements.</p>
</li>
<li><p>Are prepared to manage updates, backups, monitoring, and troubleshooting.</p>
</li>
</ul>
<h3 id="heading-choose-managed-odoo-hosting-if">Choose Managed Odoo Hosting If</h3>
<p>Managed hosting may be a better fit if you:</p>
<ul>
<li><p>Want to deploy Odoo without managing the underlying infrastructure.</p>
</li>
<li><p>Don't have dedicated IT or DevOps resources.</p>
</li>
<li><p>Prefer a provider to handle routine maintenance and infrastructure management.</p>
</li>
<li><p>Want to reduce the operational work involved in maintaining servers.</p>
</li>
<li><p>Prefer a low-maintenance solution that allows your team to focus on business operations.</p>
</li>
</ul>
<p>The right hosting model depends on how much control your organization needs and how much infrastructure management it is prepared to handle. Consider your technical skills, customization requirements, budget, and long-term business needs before making a decision.</p>
<h2 id="heading-key-factors-to-consider-before-choosing">Key Factors to Consider Before Choosing</h2>
<p>Choosing the right hosting option involves more than comparing features or costs. Consider the following factors before deciding how to host Odoo.</p>
<ul>
<li><p><strong>Budget:</strong> Consider both the initial and ongoing costs. Self-hosting requires infrastructure and maintenance, while managed hosting usually involves recurring hosting fees in exchange for less infrastructure work.</p>
</li>
<li><p><strong>Technical Expertise:</strong> Consider whether your team has the skills to install, maintain, secure, and troubleshoot Odoo. If you don't have dedicated IT or DevOps resources, managed hosting may be easier to maintain.</p>
</li>
<li><p><strong>Customization:</strong> If you need custom modules, third-party integrations, or specific server configurations, check whether your hosting option supports them. Self-hosting generally provides more control over customization.</p>
</li>
<li><p><strong>Security and Compliance:</strong> Consider your security policies, data protection requirements, and any industry regulations that apply to your organization. Also determine which security responsibilities belong to your team and which are handled by the hosting provider.</p>
</li>
<li><p><strong>Scalability:</strong> Consider how your Odoo deployment may grow over time. Your hosting environment should be able to support increases in users, data, and workloads.</p>
</li>
<li><p><strong>Maintenance and Support:</strong> Decide whether your team is prepared to manage updates, backups, monitoring, and troubleshooting or whether you would prefer a provider to handle these responsibilities.</p>
</li>
</ul>
<p>Consider these factors together rather than focusing on a single one. The right hosting solution should match your organization's technical skills, business requirements, budget, and long-term plans.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Choosing the right hosting model is an important part of planning a successful Odoo deployment. Self-hosting offers greater control, flexibility, and customization, while managed hosting reduces the effort required to maintain the infrastructure.</p>
<p>Each approach has its own advantages, and the best choice depends on your organization's technical expertise, business requirements, and long-term goals.</p>
<p>Before making a decision, evaluate factors such as your budget, customization needs, security requirements, scalability, and the resources available to manage the deployment. By selecting the hosting model that aligns with your priorities, you can build a reliable and maintainable foundation for running Odoo.</p>
<p>If you'd like to read more hands-on deployment tutorials and technical documentation, visit my portfolio at <a href="https://docs.abdultalha.dev/">docs.abdultalha.dev</a>. You can also connect with me on <a href="https://www.linkedin.com/in/abdul-talha/">LinkedIn</a> to follow my latest articles and open-source work.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Why Chrome OS Is the Operating System the AI Era Was Built For ]]>
                </title>
                <description>
                    <![CDATA[ Chrome OS runs on a read-only filesystem. You can't install executables on the host. There's no traditional desktop environment. Everything that interacts with the underlying system does so through a  ]]>
                </description>
                <link>https://www.freecodecamp.org/news/why-chrome-os-is-the-ai-os/</link>
                <guid isPermaLink="false">69e2765cfd22b8ad62611ba8</guid>
                
                    <category>
                        <![CDATA[ Chrome OS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Linux ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Cloud Computing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                    <category>
                        <![CDATA[ AWS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ cybersecurity ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Christopher Galliart ]]>
                </dc:creator>
                <pubDate>Fri, 17 Apr 2026 18:05:16 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/c4116a06-9e42-4da5-a152-0fe1433e0857.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Chrome OS runs on a read-only filesystem. You can't install executables on the host. There's no traditional desktop environment. Everything that interacts with the underlying system does so through a sandboxed browser, a containerized Linux terminal, or a cloud connection.</p>
<p>For years, that list of constraints was the reason people dismissed it. But in 2026, it's the reason Chrome OS might be the most correctly designed operating system for what's coming.</p>
<p>The security architecture treats the endpoint as untrusted by default. The containerized Linux environment gives developers a full headless stack without compromising the host. And an upcoming OS-level rewrite, Aluminium, puts Google's on-device AI models directly into the kernel.</p>
<p>This article covers security architecture, the container-based developer environment, cloud-streamed creative tools via AWS NICE DCV, cloud gaming, and what Aluminium OS means for on-device AI.</p>
<h3 id="heading-heres-what-well-cover">Here's what we'll cover:</h3>
<ol>
<li><p><a href="#heading-security-first-architecture-in-the-era-of-ai-powered-threats">Security-First Architecture in an Era of AI-Powered Threats</a></p>
</li>
<li><p><a href="#heading-a-headless-linux-stack-thats-more-flexible-than-it-looks">A Headless Linux Stack That's More Flexible Than It Looks</a></p>
</li>
<li><p><a href="#heading-aws-nice-dcv-changes-the-creative-tools-conversation">AWS NICE DCV Changes the Creative Tools Conversation</a></p>
</li>
<li><p><a href="#heading-cloud-gaming-works">Cloud Gaming Works</a></p>
</li>
<li><p><a href="#heading-aluminum-os-on-device-models-on-googles-own-architecture">Aluminium OS: On-Device Models on Google's Own Architecture</a></p>
</li>
<li><p><a href="#heading-where-this-lands">Where This Lands</a></p>
</li>
</ol>
<h2 id="heading-security-first-architecture-in-an-era-of-ai-powered-threats">Security-First Architecture in an Era of AI-Powered Threats</h2>
<p>Threat actors are getting better tools. Models like Mythos are lowering the barrier for generating convincing phishing campaigns, crafting polymorphic malware, and automating social engineering at scale.</p>
<p>Traditional operating systems present exactly the attack surface these tools target: writable system files, user-installable executables, patches that sit uninstalled for weeks because someone clicked "remind me later."</p>
<p>Chrome OS sidesteps most of this by design. The root filesystem is read-only and cryptographically verified on every boot through a process called Verified Boot.</p>
<p>If anything has modified the OS files since the last verified state, whether that's malware, a compromised package, or a rogue AI agent that decided to start deleting system files, the device detects it at startup and either self-corrects or refuses to boot.</p>
<p>Persistence across reboots isn't difficult. It's architecturally impossible through software alone.</p>
<p>Updates happen silently. While you're working, the system downloads the next OS version to an inactive partition. On your next reboot, it pivots to the updated version. No prompts, no deferred patches, no exposure window.</p>
<p>Major updates ship every four to six weeks. Security patches land every two to three weeks. The gap between vulnerability discovery and remediation is measured in days.</p>
<p>Chrome OS consistently doesn't appear in the top 50 products by CVE count in the NIST vulnerability database. Windows and the Linux kernel sit near the top every year. When AI is actively being weaponized to find and exploit vulnerabilities faster than humans can patch them, a read-only, verified, automatically updated endpoint is a different category of security posture.</p>
<p>The tradeoff is trust. Chrome OS's security model means trusting Google as the root authority for your entire computing stack: updates, certificate trust, telemetry. Organizations with strict data sovereignty requirements should weigh that dependency carefully.</p>
<h2 id="heading-a-headless-linux-stack-thats-more-flexible-than-it-looks">A Headless Linux Stack That's More Flexible Than It Looks</h2>
<p>Chrome OS is a text-based operating system. There's no native GUI layer. Stop and sit with that for a second, because it's the thing that makes people dismiss Chrome OS and also the thing that makes it work.</p>
<p>The entire graphical interface you interact with IS the Chrome browser. The Ash shell, Chrome's window manager, is the desktop. You don't install applications onto it the way you install .exe files on Windows or drag .app bundles into a macOS Applications folder. If it isn't running in a browser tab, an Android VM, or a Linux container, it doesn't run. That restriction is what keeps the host locked down, and it's what makes everything else possible.</p>
<p>Under the hood, Chrome OS runs a minimal virtual machine called Termina through crosvm, Google's Rust-based VM monitor.</p>
<p>Inside Termina, LXD manages Linux containers. The default container, penguin, is a Debian environment with a special trick: it bridges GUI-based Linux applications directly into the Chrome OS desktop through a Wayland proxy called Sommelier. Install VS Code, GIMP, or LibreOffice in penguin and they show up in your Chrome OS app launcher, running in windows alongside your browser tabs. For a lot of developers, penguin alone covers the daily workflow.</p>
<p>But Termina gives you more than penguin. Through the LXD layer you can spin up independent containers that are fully isolated operating systems: Arch, Alpine, Ubuntu, whatever you need.</p>
<p>These aren't attached to the GUI bridge. They run headless, natively, with their own systemd, their own package managers, their own persistent state. Need a clean Ubuntu environment to test a deployment script without touching your main setup? <code>lxc launch</code> and you're there. Need to blow it away? <code>lxc delete</code> and it's gone. No orphaned files on the host, no cross-contamination between environments.</p>
<p>The key distinction from Docker is that LXD runs system containers (full OS emulation) rather than application containers. You get background services, persistent daemons, the works. You can also run Docker inside any of these LXD containers if you need application-level containerization on top of that.</p>
<p>Snapshot your entire environment with <code>lxc snapshot</code> before a risky dependency install and roll back instantly if something breaks. That kind of safety net is broader than version control alone: it captures your full OS configuration, not just code.</p>
<p>Pair this with browser-native tools like GitHub Codespaces, Google Colab, AWS CloudShell, or vscode.dev, and the terminal handles your local tooling while the browser handles everything else.</p>
<p>AI coding assistants like Claude and Gemini already operate natively in the browser. The distance between "cloud IDE" and "local IDE" keeps shrinking.</p>
<p>There are friction points: no custom kernel modules inside Crostini. Nested KVM requires Intel Gen 10+ processors. VPN routing into the Linux container from the Chrome OS host can be a headache, with WireGuard requiring userspace workarounds inside the container.</p>
<p>But none of these break the core architecture for cloud-native work. They're just worth knowing about before you commit.</p>
<h2 id="heading-aws-nice-dcv-changes-the-creative-tools-conversation">AWS NICE DCV Changes the Creative Tools Conversation</h2>
<p>One of the longest-standing arguments against Chrome OS has been the absence of professional creative software. There's no Premiere, no DaVinci Resolve, no Blender, no Ableton. For years, this was a dead-end conversation.</p>
<p>AWS NICE DCV (Desktop Cloud Visualization) reopens it. DCV is a high-performance remote display protocol that streams GPU-accelerated desktop sessions from EC2 instances to any device, including a Chromebook running the browser-based DCV client. It supports OpenGL, Vulkan, and DirectX rendering, with adaptive encoding that adjusts to network conditions. On AWS, the DCV license is free. You pay only for the EC2 compute time.</p>
<p>Netflix engineers use DCV to stream content creation applications to remote artists. Volkswagen runs 3D CAD simulations across their engineering division through it. A VFX studio called RVX used it to deliver visual effects for HBO's The Last of Us, streaming Nuke, Maya, Houdini, and Blender to artists distributed across Europe from servers in Iceland. Their team said it was the best remote experience they'd ever worked with.</p>
<p>So: a Chromebook connected to a g5.xlarge EC2 instance (one A10G GPU) can run Blender, DaVinci Resolve, or any other GPU-accelerated creative application with full hardware acceleration. The rendering happens in the data center. DCV streams the pixels. The creative professional gets a responsive, high-fidelity workspace on a $400 machine that couldn't locally render a single frame.</p>
<p>The constraints are connectivity and cost. You need sustained bandwidth (25+ Mbps for 1080p work, more for 4K multi-monitor setups) and leaving a GPU instance running around the clock adds up. But for studios and professionals who already budget for high-end workstations, the math often pencils out, especially when you factor in zero local hardware maintenance and the ability to scale GPU power on demand.</p>
<h2 id="heading-cloud-gaming-works">Cloud Gaming Works</h2>
<p>GeForce NOW survived where Stadia failed because it made a better business decision: bring your own games. Connect your existing Steam, Epic, or Ubisoft library and stream from NVIDIA's server-side hardware. The Ultimate tier now runs on RTX 5080-class infrastructure. 4K at 120fps with ray tracing, on a fanless Chromebook.</p>
<p>Chrome OS has a structural advantage as a cloud gaming client. GeForce NOW runs natively in the Chromium browser via WebRTC, and users consistently report less micro-stuttering and tighter input handling than the standalone Windows desktop app. Under good network conditions, measured total latency runs 13 to 14ms, with sub-3ms ping documented near datacenter proximity. That's below human perceptual threshold for most game types.</p>
<p>Anti-cheat systems like Easy Anti-Cheat and Riot Vanguard are a non-issue in this model. They run on the server where the game executes, not on your local endpoint. On-device gaming isn't viable on Chrome OS and likely never will be. The architecture isn't designed for it, and even projects attempting to bridge local GPUs hit bottlenecks in the container layers. Cloud gaming is the path, and it works.</p>
<p>The limiting factors are network-dependent. Latency spikes above 500ms on bad connections make fast-twitch games unplayable, and NVIDIA's 100-hour monthly cap on the Ultimate tier has drawn criticism. But cloud gaming on Chrome OS has crossed the line from novelty to daily-driver viable for most use cases.</p>
<h2 id="heading-aluminium-os-on-device-models-on-googles-own-architecture">Aluminium OS: On-Device Models on Google's Own Architecture</h2>
<p>The most consequential near-term development for Chrome OS is Project Aluminium, a ground-up rewrite that replaces the current Chrome OS foundation with a native Android kernel. Not another bolted-on compatibility layer: a new operating system built on Android 16, designed to run Android applications natively with direct hardware acceleration instead of routing them through the resource-heavy ARCVM virtual machine that currently eats CPU cycles on even basic app launches.</p>
<p>The AI story is the real story. Aluminium is being built with Gemini models integrated directly into the OS: the file system, the application launcher, the window manager.</p>
<p>Google serving their own proprietary models on their own devices, using an architecture optimized specifically to run them, is a level of vertical integration that no other OS vendor has in the pipeline. Apple has the silicon advantage for local inference. Google has the model-to-OS integration advantage. Those are competing theses about where AI compute should live, and both are worth taking seriously.</p>
<p>The rollout timeline from court documents and leaked roadmaps puts a trusted tester program on select hardware in late 2026, premium tablets by early 2027, and general consumer availability in 2028. Chrome OS Classic gets maintained through existing support obligations until 2033 or 2034.</p>
<p>The launch won't be perfect. Google's track record on platform transitions gives the community earned skepticism. But the ability to iterate a natively AI-integrated OS on hardware they control is the kind of capability that compounds over time.</p>
<h2 id="heading-where-this-lands">Where This Lands</h2>
<p>Two years ago, calling Chrome OS a serious platform for development or creative work would have been a stretch. Today you can run a full Debian environment with systemd daemons, snapshot your workspace, stream Blender from a GPU-backed data center, play AAA games at 4K on hardware you don't own, and do all of it from a verified, read-only endpoint that patches itself while you sleep.</p>
<p>The remaining gaps are real. But they're concentrated in workflows that are themselves moving to the cloud. Chrome OS was designed around assumptions about computing that used to be premature. They're not premature anymore.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Run Rust on Jupyter Notebooks ]]>
                </title>
                <description>
                    <![CDATA[ If you've ever wanted to combine the power of Rust with the interactive goodness of Jupyter notebooks, you're in the right place. Maybe you're tired of compiling every single time you want to test a s ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-run-rust-on-jupyter-notebooks/</link>
                <guid isPermaLink="false">699879483dc17c4862f498f9</guid>
                
                    <category>
                        <![CDATA[ Rust ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Jupyter Notebook  ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Linux ]]>
                    </category>
                
                    <category>
                        <![CDATA[ WSL ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Tutorial ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Daniel Iwugo ]]>
                </dc:creator>
                <pubDate>Fri, 20 Feb 2026 15:10:00 +0000</pubDate>
                <media:content url="https://cloudmate-test.s3.us-east-1.amazonaws.com/uploads/covers/5e1e335a7a1d3fcc59028c64/6e411f5d-65a1-407d-a4f0-0beceb1e784b.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>If you've ever wanted to combine the power of Rust with the interactive goodness of Jupyter notebooks, you're in the right place. Maybe you're tired of compiling every single time you want to test a snippet, learn Rust in a more interactive way, or just have a crazy idea pop into your head like I do.</p>
<p>Most people think Jupyter is just for Python and data science stuff, but apparently you can run Rust in one, too.</p>
<p>In this tutorial, we’ll be taking a look at:</p>
<ol>
<li><p><a href="#heading-what-is-evcxr">What is EvCxR?</a></p>
</li>
<li><p><a href="#heading-how-to-install-the-rust-jupyter-kernel">How to Install the Rust Jupyter kernel</a></p>
</li>
<li><p><a href="#heading-step-4-write-your-first-rust-code">How to run your first Rust code in a notebook</a></p>
</li>
<li><p><a href="#heading-handy-tips-and-tricks">Handy Tips and Tricks</a></p>
</li>
<li><p><a href="#heading-common-issues-and-solutions">Common Issues and Solutions</a></p>
</li>
<li><p><a href="#heading-when-not-to-use-jupyter-for-rust">When NOT to Use Jupyter for Rust</a></p>
</li>
</ol>
<p><strong>Friendly Disclaimer</strong>: This tutorial assumes you know the basics of both Rust and Jupyter. If you break something, that's on you, mate 🙂.</p>
<p>So without further ado, let's jump in.</p>
<h2 id="heading-what-is-evcxr"><strong>What is EvCxR?</strong></h2>
<p>EvCxR (pronounced "Evaluator" to my fellow linguists’ horror) is a Rust REPL and Jupyter kernel. It's basically the magic that lets you run Rust code interactively in Jupyter notebooks instead of the traditional compile-run-debug cycle.</p>
<p>The name stands for "Evaluation Context for Rust", and it’s an open source project actively maintained on GitHub. Here are a few things that make this terribly named tool absolutely brilliant:</p>
<ol>
<li><p><strong>Interactive development:</strong> It lets you test Rust snippets without creating a whole project 🧪</p>
</li>
<li><p><strong>Prototyping:</strong> You can quickly try out ideas before committing to a full implementation 💡</p>
</li>
<li><p><strong>Data visualisation:</strong> And yes, you can even plot charts with Rust (more on that later) 📊</p>
</li>
</ol>
<h2 id="heading-how-to-install-the-rust-jupyter-kernel">How to Install the Rust Jupyter kernel</h2>
<h3 id="heading-prerequisites"><strong>Prerequisites</strong></h3>
<p>Before we dive into the installation, make sure you have these sorted:</p>
<ol>
<li><p><strong>A Linux System:</strong> Or at least, Windows Subsystem for Linux (There’s a little note below for Windows users.)</p>
</li>
<li><p><strong>The Rust toolchain:</strong> You can get it from <a href="https://rustup.rs/">rustup.rs</a> if you haven't already</p>
</li>
<li><p><strong>Jupyter:</strong> Install via pip – <code>pip install jupyter</code></p>
</li>
<li><p><strong>Patience:</strong> This might take a minute or two ⏱️</p>
</li>
</ol>
<p>Once you’ve got all that, we can get rusty (pun intended).</p>
<p><strong>Note:</strong> If you’re using Windows, you’ll need to do a little extra to get started. Here’s the quick rundown:</p>
<ol>
<li><p>Go to <a href="https://visualstudio.microsoft.com/visual-cpp-build-tools/">https://visualstudio.microsoft.com/visual-cpp-build-tools/</a></p>
</li>
<li><p>Download and run the installer</p>
</li>
<li><p>Select <strong>"Desktop development with C++"</strong></p>
</li>
<li><p>Install it (it's large, ~5GB)</p>
</li>
</ol>
<h3 id="heading-step-1-install-evcxr"><strong>Step 1: Install EvCxR</strong></h3>
<p>Open your terminal and run this command:</p>
<pre><code class="language-rust">cargo install evcxr_jupyter
</code></pre>
<p>Now go grab a cup of joe ☕. This will take a few minutes as Cargo downloads and compiles everything. And don't panic if it seems stuck. Rust compilation is thorough but not particularly fast.</p>
<p>If you get any errors about missing system libraries, you might need to install some dependencies. On Ubuntu/Debian, try:</p>
<pre><code class="language-bash">sudo apt install jupyter-notebook jupyter-core python-ipykernel
sudo apt install cmake
</code></pre>
<p>On macOS with Homebrew:</p>
<pre><code class="language-bash">brew install cmake jupyter
</code></pre>
<h3 id="heading-step-2-install-the-jupyter-kernel"><strong>Step 2: Install the Jupyter Kernel</strong></h3>
<p>Once the installation finishes, you’ll need to register the EvCxR kernel with Jupyter:</p>
<pre><code class="language-bash">evcxr_jupyter --install
</code></pre>
<p>You should see output that looks something like this at the end:</p>
<pre><code class="language-plaintext">Installation complete
</code></pre>
<h3 id="heading-step-3-launch-jupyter-and-create-a-rust-notebook"><strong>Step 3: Launch Jupyter and Create a Rust Notebook</strong></h3>
<p>Let’s test out our baby. Fire up Jupyter:</p>
<pre><code class="language-bash">jupyter notebook
</code></pre>
<p>Your browser should open automatically (if it doesn't, copy the URL from the terminal).</p>
<p>In the Jupyter interface:</p>
<ol>
<li><p>Click <strong>New</strong> in the top right</p>
</li>
<li><p>Select <strong>Rust</strong> from the dropdown (or "evcxr" depending on your version)</p>
</li>
<li><p>A new notebook opens</p>
</li>
</ol>
<p>Welcome to interactive Rust! 🦀</p>
<h3 id="heading-step-4-write-your-first-rust-code"><strong>Step 4: Write Your First Rust Code</strong></h3>
<p>Let's start with a classic:</p>
<pre><code class="language-rust">println!("Hello my fellow Rustaceans! 🦀");
</code></pre>
<p>Hit <code>Shift + Enter</code> to run the cell. You should see the output appear below the cell. Simple as that.</p>
<p>Note that notebooks execute code at the top level, so you don’t have to wrap it around the <code>main()</code> function. If you still want to do that, you’re going to have to call it like this:</p>
<pre><code class="language-rust">fn main(){
    println!("Hello my fellow Rustaceans! 🦀");
}
//Calling the function
main()
</code></pre>
<p>Now let's try something more interesting:</p>
<pre><code class="language-rust">fn fibonacci(n: u32) -&gt; u32 {
    match n {
        0 =&gt; 0,
        1 =&gt; 1,
        _ =&gt; fibonacci(n - 1) + fibonacci(n - 2)
    }
}

for i in 0..10 {
    println!("fibonacci({}) = {}", i, fibonacci(i));
}
</code></pre>
<p>Run it and watch the Fibonacci sequence appear.</p>
<pre><code class="language-plaintext">fibonacci(0) = 0
fibonacci(1) = 1
fibonacci(2) = 1
fibonacci(3) = 2
fibonacci(4) = 3
fibonacci(5) = 5
fibonacci(6) = 8
fibonacci(7) = 13
fibonacci(8) = 21
fibonacci(9) = 34
</code></pre>
<h2 id="heading-handy-tips-and-tricks"><strong>Handy Tips and Tricks</strong></h2>
<p>Functions aren’t the only things that behave differently when using Rust in notebooks. Here are a few other things you might want to keep in mind:</p>
<h3 id="heading-variables-persist-between-cells">Variables Persist Between Cells</h3>
<p>Unlike traditional Rust compilation, variables you define in one cell stick around for the next cells:</p>
<pre><code class="language-rust">let mut counter = 0;
</code></pre>
<p>Then in the next cell:</p>
<pre><code class="language-rust">counter += 1;
println!("Counter: {}", counter);
</code></pre>
<p>The output would be:</p>
<pre><code class="language-plaintext">Counter: 1
</code></pre>
<p>This is great for building up complex examples step by step.</p>
<h3 id="heading-you-can-use-external-crates">You Can Use External Crates</h3>
<p>Add dependencies with the <code>:dep</code> command in one cell:</p>
<pre><code class="language-rust">:dep serde = { version = "1.0", features = ["derive"] }
:dep serde_json = "1.0"
</code></pre>
<p>Then use them normally in the next:</p>
<pre><code class="language-rust">use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize, Debug)]
struct Person {
    name: String,
    age: u32,
}

let person = Person {
    name: "Amina".to_string(),
    age: 24,
};

let json = serde_json::to_string(&amp;person).unwrap();
println!("{}", json);
</code></pre>
<p>Output:</p>
<pre><code class="language-plaintext">{"name":"Amina","age":24}
</code></pre>
<p>Pretty neat, huh?</p>
<h3 id="heading-visualisation-support">Visualisation Support</h3>
<p>You can even create graphs. To get started, install the <code>plotters</code> crate:</p>
<pre><code class="language-rust">:dep plotters = { version = "0.3", default-features = false, features = ["evcxr", "all_series", "bitmap_backend", "bitmap_encoder"] }
</code></pre>
<p>Then create a simple sine graph:</p>
<pre><code class="language-rust">use plotters::prelude::*;

let root = SVGBackend::new("sine_wave.svg", (640, 480)).into_drawing_area();
root.fill(&amp;WHITE).unwrap();

let mut chart = ChartBuilder::on(&amp;root)
    .caption("Sine Wave", ("Arial", 20))
    .margin(5)
    .x_label_area_size(30)
    .y_label_area_size(30)
    .build_cartesian_2d(-3.14..3.14, -1.2..1.2)
    .unwrap();

chart.configure_mesh().draw().unwrap();

chart.draw_series(LineSeries::new(
    (-314..314).map(|x| {
        let x = x as f64 / 100.0;
        (x, x.sin())
    }),
    &amp;RED,
)).unwrap();

root.present().unwrap();
println!("Plot saved to sine_wave.svg");
</code></pre>
<p>Output:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1771272251271/c07b1c22-4ea1-408c-984a-4179a47058d9.png" alt="Sine wave graph showing output of the code" style="display: block;" width="600" height="400" loading="lazy">

<p><strong>A word on plotting:</strong> You can actually display plots directly inline in your notebook. But if you're using WSL with VSCode (like I do), inline plotting may not work properly due to rendering issues on the notebook interface. That’s why I used it as an svg file that I can easily view in my text editor.</p>
<h3 id="heading-checking-types">Checking Types</h3>
<p>Not sure what type something is? Use <code>:vars</code>. This shows all variables and their types:</p>
<pre><code class="language-rust">let x = vec![1, 2, 3];
</code></pre>
<pre><code class="language-rust">:vars
</code></pre>
<p>Output:</p>
<pre><code class="language-plaintext">Variable	    Type
       x	Vec&lt;i32&gt;
</code></pre>
<h2 id="heading-common-issues-and-solutions">Common Issues and Solutions</h2>
<h3 id="heading-compilation-errors-everywhere">Compilation Errors Everywhere</h3>
<p>If you're getting weird compilation errors, remember:</p>
<ul>
<li><p>Each cell is compiled separately</p>
</li>
<li><p>You might need to reimport things in each cell</p>
</li>
</ul>
<h3 id="heading-slow-execution">Slow Execution</h3>
<p>The first time you run code in a session, it's slow due to the compilation overhead. Subsequent runs are faster. If it's really slow, you might want to:</p>
<ul>
<li><p>Use release mode: <code>:opt 2</code></p>
</li>
<li><p>Reduce dependency features to only what you need</p>
</li>
<li><p>Consider if Jupyter is the right tool for your use case</p>
</li>
</ul>
<h3 id="heading-dependencies-not-loading">Dependencies Not Loading</h3>
<p>If a crate won't load:</p>
<ul>
<li><p>Make sure the version exists on <a href="http://crates.io">crates.io</a></p>
</li>
<li><p>Check your internet connection (it needs to download)</p>
</li>
<li><p>Try specifying features explicitly</p>
</li>
<li><p>Clear the cargo cache if things get really wonky: <code>rm -rf ~/.evcxr</code></p>
</li>
</ul>
<h2 id="heading-when-not-to-use-jupyter-for-rust"><strong>When NOT to Use Jupyter for Rust</strong></h2>
<p>Jupyter notebooks are great for learning and experimenting, but they're not always the best choice in:</p>
<ul>
<li><p><strong>Production code:</strong> Use proper projects with cargo</p>
</li>
<li><p><strong>Performance-critical code:</strong> The overhead isn't worth it</p>
</li>
<li><p><strong>Large applications:</strong> Notebooks get very messy, very fast</p>
</li>
<li><p><strong>Team collaboration:</strong> Version control with notebooks is quite the nightmare</p>
</li>
</ul>
<p>Stick to notebooks for prototyping and quick experiments. For anything serious, fire up your favourite editor and create a proper Rust project.</p>
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>Let's summarise what you've learned:</p>
<ol>
<li><p>How to install the EvCxR Jupyter kernel</p>
</li>
<li><p>How to create and run Rust notebooks</p>
</li>
<li><p>How to use external crates in notebooks</p>
</li>
<li><p>Tips and tricks for interactive Rust development</p>
</li>
</ol>
<p>Jupyter notebooks make Rust more accessible for learning and experimentation. Give it a go next time you want to try out a quick Rust snippet without the ceremony of creating a full project. And with that, we've come to the end of this tutorial.</p>
<p>Cheers.</p>
<h2 id="heading-resources">Resources</h2>
<ol>
<li><p><a href="https://github.com/evcxr/evcxr">EvCxR GitHub Repository</a></p>
</li>
<li><p><a href="https://doc.rust-lang.org/book/">Rust Book</a></p>
</li>
<li><p><a href="https://jupyter.org/documentation">Jupyter Documentation</a></p>
</li>
</ol>
<h2 id="heading-acknowledgements">Acknowledgements</h2>
<p>Thanks to <a href="https://www.linkedin.com/in/a-n-u-o/">Anuoluwapo Victor</a>, <a href="https://www.linkedin.com/in/a-n-u-o/">Chinaza Nwukwa,</a> <a href="https://www.linkedin.com/in/chinaza-nwukwa-22a256230/">Holumidey Mer</a><a href="https://www.linkedin.com/in/mercy-holumidey-88a542232/">cy</a>, <a href="https://www.linkedin.com/in/mercy-holumidey-88a542232/">Favour Ojo,</a> <a href="https://www.linkedin.com/in/favour-ojo-906883199/">Georgina</a> <a href="https://www.linkedin.com/in/georgina-awani-254974233/">Awani</a>, <a href="https://www.linkedin.com/in/georgina-awani-254974233/">and my family</a> for the inspiration, support and knowledge used to put this post together.</p>
<p>And thanks to the EvCxR project maintainers for making this possible, the Rust community for being awesome, and to anyone reading this for wanting to learn. You inspire me daily.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Use the NixOS Linux Distro – A Tutorial for Developers ]]>
                </title>
                <description>
                    <![CDATA[ NixOS is a Linux distribution based on the Nix package manager and the Nix language. It’s first stable release was in 2013, and it uses a declarative, reproducible system configuration that allows atomic upgrades and rollbacks. The Nix language is a ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-use-the-nixos-linux-distro-a-tutorial-for-developers/</link>
                <guid isPermaLink="false">6967bce8f1306e271c8038cf</guid>
                
                    <category>
                        <![CDATA[ NixOS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Nix ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Linux ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Rajdeep Singh ]]>
                </dc:creator>
                <pubDate>Wed, 14 Jan 2026 15:57:28 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1768330530946/99ecef9a-4654-4281-9443-2039455c121e.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>NixOS is a Linux distribution based on the Nix package manager and the Nix language. It’s first stable release was in 2013, and it uses a declarative, reproducible system configuration that allows atomic upgrades and rollbacks.</p>
<p>The Nix language is a specialized, purely functional programming language. It’s used by the Nix package manager to build packages and the NixOS operating system for declarative system configuration and software packaging. </p>
<p>Unlike traditional Linux distributions, NixOS utilizes the Nix programming language to describe the entire system, including packages, services, users, networking, and even the bootloader – all of which are defined through a declarative configuration. This approach enables NixOS to generate complete system profiles, allowing for reproducible deployments, atomic upgrades, and easier system rollbacks.</p>
<p>In simpler terms, in NixOS, you can configure your programs, services, and users, and install new system-wide packages or applications directly within the <code>configuration.nix</code> file – which you can then share directly with others.</p>
<p>Also, if anything goes wrong with your current NixOS generation during the system build time, you can roll back to a previous NixOS generation (after switching to a new generation – more on this below).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1768044273038/62862f39-0706-4611-8a91-0b3ab2839a02.png" alt="NixOS - what it is, and what it is not" class="image--center mx-auto" width="3000" height="1500" loading="lazy"></p>
<p>In this tutorial, I’ll explain in detail what NixOS is, how it works, its benefits, and how to set it up on your machine or laptop in a beginner-friendly way.</p>
<h2 id="heading-table-of-contents"><strong>Table of Contents:</strong></h2>
<ol>
<li><p><a target="_blank" href="https://preview.freecodecamp.org/69416680eb9d6846d92f037e#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-is-a-declarative-configuration">What is a Declarative Configuration?</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-why-is-the-declarative-approach-declarative-configuration-used-in-nixos">Why is the Declarative Approach (Declarative Configuration) used in NixOS?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-are-reproducible-systems">What Are Reproducible Systems?</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-how-does-nixos-work">How Does NixOS Work?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-are-the-benefits-of-using-nixos">What Are the Benefits of Using NixOS?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-when-would-nixos-not-be-the-best-choice">When Would NixOS Not Be the Best Choice?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-set-up-nixos-and-the-nix-package-manager-on-your-laptop">How to Set Up NixOS and the Nix Package Manager on Your Laptop</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-install-a-package-in-nixos">How to Install a Package in NixOS?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-faq">FAQ</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ol>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>To work with NixOS and the Nix package manager, there are no specific prerequisites if you have at least a couple years of experience with Ubuntu, Debian, or any other distribution. Having some basic knowledge of the Nix language is a plus.</p>
<h2 id="heading-what-is-a-declarative-configuration">What is a Declarative Configuration?</h2>
<p>In the declarative approach, we use a file (such as a YAML, JSON, or Nix) to describe the configuration for hardware and software components, such as systems, networking, users, boot loader, services (like with systems), and more, in one file.</p>
<p>NixOS uses the <code>configuration.nix</code> file for its declarative configuration. By default, the <code>configuration.nix</code> file looks like this:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># /etc/nixos/configuration.nix</span>

{ config, pkgs, ... }:
{

  imports = [
      ./hardware-configuration.nix
   ];

  boot.loader.systemd-boot.enable = <span class="hljs-literal">true</span>;
  boot.loader.efi.canTouchEfiVariables = <span class="hljs-literal">true</span>;

  networking.hostName = <span class="hljs-string">"nixos"</span>; <span class="hljs-comment"># Define your hostname.</span>

  <span class="hljs-comment"># Enable networking</span>
  networking.networkmanager.enable = <span class="hljs-literal">true</span>;

  <span class="hljs-comment"># Set your time zone.</span>
  time.timeZone = <span class="hljs-string">"Asia/Kolkata"</span>;

  <span class="hljs-comment"># Select internationalisation properties.</span>
  i18n.defaultLocale = <span class="hljs-string">"en_IN"</span>;

  i18n.extraLocaleSettings = {
    LC_ADDRESS = <span class="hljs-string">"en_IN"</span>;
    LC_IDENTIFICATION = <span class="hljs-string">"en_IN"</span>;
    LC_MEASUREMENT = <span class="hljs-string">"en_IN"</span>;
    LC_MONETARY = <span class="hljs-string">"en_IN"</span>;
    LC_NAME = <span class="hljs-string">"en_IN"</span>;
    LC_NUMERIC = <span class="hljs-string">"en_IN"</span>;
    LC_PAPER = <span class="hljs-string">"en_IN"</span>;
    LC_TELEPHONE = <span class="hljs-string">"en_IN"</span>;
    LC_TIME = <span class="hljs-string">"en_IN"</span>;
  };

  <span class="hljs-comment"># Enable the X11 windowing system.</span>
  services.xserver.enable = <span class="hljs-literal">true</span>;

  <span class="hljs-comment"># Enable the GNOME Desktop Environment.</span>
  services.xserver.displayManager.gdm.enable = <span class="hljs-literal">true</span>;
  services.xserver.desktopManager.gnome.enable = <span class="hljs-literal">true</span>;

  <span class="hljs-comment"># remove preinstall or  unused package in gnome</span>
  environment.gnome.excludePackages = with pkgs; [ gnome-tour gnome.gnome-music nixos-render-docs  ];
  services.xserver.excludePackages = with  pkgs; [ xterm ];

  <span class="hljs-comment"># Configure keymap in X11</span>
  services.xserver.xkb = {
    layout = <span class="hljs-string">"us"</span>;
    variant = <span class="hljs-string">""</span>;
  };

  <span class="hljs-comment"># Enable sound with pipewire.</span>
  sound.enable = <span class="hljs-literal">true</span>;
  hardware.pulseaudio.enable = <span class="hljs-literal">false</span>;
  security.rtkit.enable = <span class="hljs-literal">true</span>;
  services.pipewire = {
    <span class="hljs-built_in">enable</span> = <span class="hljs-literal">true</span>;
    alsa.enable = <span class="hljs-literal">true</span>;
    alsa.support32Bit = <span class="hljs-literal">true</span>;
    pulse.enable = <span class="hljs-literal">true</span>;
  };

  <span class="hljs-comment"># Define a user account. Don't forget to set a password with ‘passwd’.</span>
  users.users.officialrajdeepsingh = {
    isNormalUser = <span class="hljs-literal">true</span>;
    description = <span class="hljs-string">"officialrajdeepsingh"</span>;
    extraGroups = [ <span class="hljs-string">"networkmanager"</span> <span class="hljs-string">"wheel"</span> <span class="hljs-string">"docker"</span> ];
    packages = with pkgs; [
      google-chrome
    ];
  };

  <span class="hljs-comment"># Enable automatic login for the user.</span>
  services.xserver.displayManager.autoLogin.enable = <span class="hljs-literal">true</span>;
  services.xserver.displayManager.autoLogin.user = <span class="hljs-string">"officialrajdeepsingh"</span>;

  <span class="hljs-comment"># Workaround for GNOME autologin: https://github.com/NixOS/nixpkgs/issues/103746#issuecomment-945091229</span>
  systemd.services.<span class="hljs-string">"getty@tty1"</span>.<span class="hljs-built_in">enable</span> = <span class="hljs-literal">false</span>;
  systemd.services.<span class="hljs-string">"autovt@tty1"</span>.<span class="hljs-built_in">enable</span> = <span class="hljs-literal">false</span>;


  services.openssh = {
      <span class="hljs-built_in">enable</span> = <span class="hljs-literal">true</span>;
      settings = {
        PasswordAuthentication = <span class="hljs-literal">true</span>;
      };
  };
  system.stateVersion = <span class="hljs-string">"23.05"</span>; <span class="hljs-comment"># Did you read the comment?</span>
}
</code></pre>
<p>You can edit the configuration.nix file to easily enable NGINX and Git using NixOS options. NixOS options let you choose whether to turn features on or off and configure how they should work.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># /etc/nixos/configuration.nix</span>

services.nginx.enable = <span class="hljs-literal">true</span>;
programs.git.enable = <span class="hljs-literal">true</span>;

....
</code></pre>
<p>Every time you modify the <code>configuration.nix</code> file to apply changes to NixOS, you’ll need to build your NixOS using the following command:</p>
<pre><code class="lang-bash">sudo nixos-rebuild switch
</code></pre>
<p>The <code>nixos-rebuild</code> command generates a new NixOS generation based on your configuration file. This <strong>generation</strong> is a complete, immutable snapshot of your system's configuration (packages, services, settings) that’s created every time you run that <code>nixos-rebuild</code> command.</p>
<p>The switch flag helps to build and activate the new generation at the same time, and make it the default boot in NixOS.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1768129947654/a21a336e-277e-4566-8b69-d4574ef86c27.png" alt="NixOS generation list" class="image--center mx-auto" width="992" height="718" loading="lazy"></p>
<p>After a system update, if the new generation isn’t desirable, you can roll back or switch to a previous generation using the <code>nixos-rebuild switch --rollback</code> command. For example, if I’m currently on generation 22, which is the default boot, and I don't like this generation, I can use the <code>rollback</code> flag to switch back to generation 21 in NixOS.</p>
<p>The NixOS rollback feature helps you test new functions and features to see if they work properly when you install new applications or programs on NixOS.</p>
<h3 id="heading-why-is-the-declarative-approach-declarative-configuration-used-in-nixos">Why is the <strong>Declarative Approach (Declarative Configuration) used in NixOS?</strong></h3>
<p>The Declarative Approach is particularly useful because it allows you to share your NixOS configuration file with other developers using GitHub and GitLab. By using the <code>configuration.nix</code> file, other developers can build the same system or machine (making it reproducible).</p>
<p>This approach also helps you manage configurations because all your settings are in one place, making it easy to adjust them at any time.</p>
<h3 id="heading-what-are-reproducible-systems">What Are Reproducible Systems?</h3>
<p>This concept of reproducibility is important. In NixOS, we can achieve reproducibility using the <code>configuration.nix</code> file. For example, when two developers use the same NixOS configuration file on their machines, they can achieve highly reproducible and consistent system setups.</p>
<p>This is one feature that makes NixOS so useful for developers, teams, CI/CD, servers, and DevOps. With NixOS, you can avoid the common issue of "it doesn't work on my machine."</p>
<h2 id="heading-how-does-nixos-work">How Does NixOS Work?</h2>
<p>NixOS works differently compared to traditional Linux distributions. In traditional Linux distros, such as Ubuntu and Debian, you can use the apt command to install a new application or program in your distro, like this:</p>
<pre><code class="lang-bash">sudo apt install git <span class="hljs-comment"># Install git package</span>

sudo apt install nodejs <span class="hljs-comment"># Install node.js package</span>

sudo apt install npm  <span class="hljs-comment"># Install NPM package</span>
</code></pre>
<p>But as we discussed above, NixOS uses a declarative configuration approach that’s immutable, reproducible, and portable.</p>
<p>This means that you can’t install any packages or programs like Git, Chrome, Firefox, Node, Deno, Bun, and so on using the apt, dkpg, or pacman commands (as NixOS uses its own package manager, Nix).</p>
<p>Instead, you edit the <code>configuration.nix</code> file and mention your package and program, such as Node.js, NGINX, or Git in the file and rebuild your NixOS using the nixos-rebuild command.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># /etc/nixos/configuration.nix</span>

services.nginx.enable = <span class="hljs-literal">true</span>;
programs.git.enable = <span class="hljs-literal">true</span>;

environment.systemPackages = [
  pkgs.nodejs_24
];
</code></pre>
<p>Again, this creates a new generation every time you modify the NixOS configuration and run the <code>nixos-rebuild</code> command in your system.</p>
<p>To understand in more detail how NixOS works, check out this <a target="_blank" href="https://nixos.org/guides/how-nix-works/">more in-depth tutorial</a>.</p>
<h2 id="heading-what-are-the-benefits-of-using-nixos">What Are the Benefits of Using NixOS?</h2>
<p>There are many benefits to using NixOS over a traditional distro, some of which I’ve already mentioned. Let’s summarize them here:</p>
<ol>
<li><p><strong>Declarative configuration</strong>: All settings for your system, including programs, applications, and services, are written in a single configuration file rather than being installed manually.</p>
</li>
<li><p><strong>Instant rollbacks</strong>: There's no need to worry about breaking your system. If something goes wrong during an update, you can easily revert to the previous version.</p>
</li>
<li><p><strong>Safe updates</strong>: Updates either complete successfully or don’t apply at all, ensuring your system never ends up in a half-broken state.</p>
</li>
<li><p><strong>Reproducible systems</strong>: With the same configuration, you can recreate the same system every time, eliminating issues like "it doesn’t work on my machine."</p>
</li>
<li><p><strong>No dependency conflicts</strong>: Multiple versions of applications can coexist without issues, allowing different programs such as Node.js and Python to operate together seamlessly.</p>
</li>
<li><p><strong>Extensive package ecosystem</strong>: The Nix Packages collection comprises thousands of up-to-date packages maintained by the NixOS community.</p>
</li>
<li><p><strong>Setting up a new machine</strong>: Copy your configuration file, rebuild the system, and complete the setup in just a few minutes, whether for a laptop or a server.</p>
</li>
<li><p><strong>Immutable system</strong>: The design of an immutable system keeps core system paths unchanged. This prevents accidental alterations and enhances reliability, as core components become read-only and cannot be modified after the initial build.</p>
</li>
</ol>
<h2 id="heading-when-would-nixos-not-be-the-best-choice">When Would NixOS Not Be the Best Choice?</h2>
<p>There are various situations where NixOS may not be the best choice for you. Here are some of the main issues:</p>
<ol>
<li><p>NixOS has a steep learning curve, particularly for beginner and intermediate developers.</p>
</li>
<li><p>It doesn’t offer a simple one-click installation solution for applications and programs on your machine or laptop.</p>
</li>
<li><p>You can’t install system-wide applications or programs without editing the NixOS configuration files and rebuilding the system.</p>
</li>
<li><p>NixOS lacks a larger community and readily available tutorials compared to Ubuntu, and its documentation is not very beginner-friendly – so you may need to rely on your own resources.</p>
</li>
</ol>
<h2 id="heading-how-to-set-up-nixos-and-the-nix-package-manager-on-your-laptop">How to Set Up NixOS and the Nix Package Manager on Your Laptop</h2>
<p>Now we’re ready to dive in and set up NixOS. But what you need to install depends on your operating system:</p>
<ul>
<li><p>On macOS or Windows, you’ll install Nix, the package manager. This lets you use Nix to install and manage software on your existing operating system. You <strong>don’t</strong> install NixOS itself on macOS or Windows.</p>
</li>
<li><p>On Linux, installing NixOS means installing a new OS (it’s like installing Ubuntu or Debian).</p>
</li>
</ul>
<p>Because NixOS is a full operating system, you’ll need to install it on a fresh machine or partition, which will typically erase existing data during installation unless you set up dual-booting.</p>
<p>And remember, while NixOS is a Linux distribution, it works differently (than Ubuntu or Debian, for example) because the entire system is configured declaratively using Nix.</p>
<h3 id="heading-install-nix-package-manager">Install Nix Package Manager</h3>
<p>The following command helps you to install the Nix language and the Nix Package Manager on macOS and Windows (via WSL).</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Windows: Multi-user installation (recommended)</span>
sh &lt;(curl --proto <span class="hljs-string">'=https'</span> --tlsv1.2 -L https://nixos.org/nix/install) --daemon

<span class="hljs-comment"># Windows: Single-user installation</span>
sh &lt;(curl --proto <span class="hljs-string">'=https'</span> --tlsv1.2 -L https://nixos.org/nix/install) --no-daemon

<span class="hljs-comment"># MacOS:</span>
sh &lt;(curl --proto <span class="hljs-string">'=https'</span> --tlsv1.2 -L https://nixos.org/nix/install)
</code></pre>
<p>If you’re a newcomer, before running the command I recommend <a target="_blank" href="https://nixos.org/download/#nix-install-macos">watching this tutorial on YouTube</a> and checking out the official documentation.</p>
<h3 id="heading-install-nixos">Install NixOS</h3>
<p>You can install the NixOS distro with a Linux command. Before proceeding, make sure you meet the following requirements:</p>
<ul>
<li><p>A USB drive (8 GB or more)</p>
</li>
<li><p>A second computer (to create the USB)</p>
</li>
<li><p>A backup of your data (installation can erase the disk)</p>
</li>
<li><p>An internet connection (Wi-Fi or Ethernet)</p>
</li>
</ul>
<p>There are multiple steps for installing NixOS on your machine or laptop. You can check out the following tutorial, which describes in detail how you can install NixOS on your machine very easily.</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/N39_cg8QyT4" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<p> </p>
<h2 id="heading-how-to-install-a-package-in-nixos">How to Install a Package in NixOS</h2>
<p>NixOS has a large registry of active packages, with 120,000 packages available. Every package you install from the stable channel is built reproducibly and reviewed by the Nix community, so you shouldn’t encounter any issues.</p>
<p>Installing a new package on NixOS using the Nix Package Manager is quite simple. First, visit the <a target="_blank" href="https://search.nixos.org/packages">NixOS Packages Search</a> site.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767107353823/ae26031b-5395-4e36-87ed-d7b4cdd2be9f.png" alt="Search package on NixOS packages website" class="image--center mx-auto" width="1920" height="961" loading="lazy"></p>
<p>Then just search for the package that you’re looking for. In our case, we’ll search for Neovim – and then just type the package name in the search input and hit enter.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1768142068118/34333980-65ae-4231-a7a2-9919e3135bcd.png" alt="Search for neovim on the packages website" class="image--center mx-auto" width="1920" height="961" loading="lazy"></p>
<p>Copy the resulting code it shows, and paste it into your <code>configuration.nix</code> file:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># /etc/nixos/configuration.nix</span>

environment.systemPackages = [
  pkgs.neovim <span class="hljs-comment"># add inside file.</span>
];
</code></pre>
<p>Then rebuild your NixOS using the <code>sudo nixos-rebuild switch</code> command. Remember that you’ll need to do this whenever you make changes to the <code>configuration.nix</code> file.</p>
<h3 id="heading-demo-how-to-install-neovim-in-nixos">Demo (How to Install Neovim in NixOS)</h3>
<p><a target="_blank" href="https://www.youtube.com/watch?v=wFP9CbaeMe0"><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1768146169478/d941876f-abf0-49f6-b1cc-182c608c094e.gif" alt="Screenshot of a text editor displaying a configuration file with commented instructions and settings for installing software packages like Firefox on a system. The interface includes options such as Exit, Save, and Execute at the bottom. Copyright by Low Orbit Flux" class="image--center mx-auto" width="640" height="360" loading="lazy"></a></p>
<h2 id="heading-faq">FAQ</h2>
<h3 id="heading-is-nixos-only-for-advanced-users">Is NixOS only for advanced users?</h3>
<p>No, NixOS can be a great fit for anyone. Due to its steeper learning curve, beginners may struggle at first – but once you understand it, I bet you’ll love it.</p>
<h3 id="heading-why-is-the-nix-language-so-weird">Why is the Nix language so weird?</h3>
<p>Nix is purely functional and is designed for reproducible builds. It’s not a general-purpose language and is rather a configuration language. You’ll only need 10–15% of the language for daily use.</p>
<h3 id="heading-how-is-nixos-different-from-ubuntu-arch">How is NixOS different from Ubuntu / Arch?</h3>
<p>Let’s compare some important NixOS features with other distros:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Feature</td><td>Ubuntu / Arch</td><td>NixOS</td></tr>
</thead>
<tbody>
<tr>
<td>Install apps</td><td>Manual</td><td>Declarative</td></tr>
<tr>
<td>Rollbacks</td><td>No</td><td>Yes</td></tr>
<tr>
<td>System config</td><td>Spread everywhere</td><td>One file</td></tr>
<tr>
<td>Reproducibility</td><td>Hard</td><td>Built-in</td></tr>
<tr>
<td>Learning curve</td><td>Low</td><td>High</td></tr>
</tbody>
</table>
</div><h3 id="heading-can-i-use-nixos-for-development">Can I use NixOS for development?</h3>
<p>Yes, NixOS is an excellent distro for:</p>
<ul>
<li><p>Frontend development (Node, Bun, Deno)</p>
</li>
<li><p>Backend development (Go, Rust, Python)</p>
</li>
<li><p>Consistent development environments across machines</p>
</li>
<li><p>CI/CD reproducibility</p>
</li>
</ul>
<h3 id="heading-is-nixos-good-for-daily-use">Is NixOS good for daily use?</h3>
<p>Yes – NixOS has an initial learning phase that can be difficult, but once you get past it, you can use NixOS on your work laptops, servers, home PCs, and development machines.</p>
<h3 id="heading-should-i-learn-nixos-as-a-beginner-developer">Should I learn NixOS as a beginner developer?</h3>
<p>To be honest, if you want quick results, NixOS may not be for you. But if you're aiming for long-term mastery, you will definitely like NixOS.</p>
<h3 id="heading-does-nixos-work-on-macos-and-windows">Does NixOS work on macOS and Windows?</h3>
<p>Yes, you can use Nix and the Nix Package Manager on macOS and Windows, and they work well. You can install and package software using the Nix configuration file as mentioned in this tutorial.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>I think that NixOS is the best Linux distro – but it’s not super beginner-friendly. Still, I prefer it because it’s a powerful and reliable distro that’s designed for users who value control, reproducibility, and safety.</p>
<p>Managing the entire system through declarative configuration enables consistent setups, safe upgrades, and easy rollbacks. This makes it especially suitable for developers, DevOps engineers, and infrastructure-focused teams.</p>
<p>Just keep in mind that NixOS is not for everyone. Its learning curve and configuration-driven workflow can be steep for beginners, casual users, or those who want quick, click-and-install convenience. So check it out and decide if it’s right for you and your team.</p>
<p>To Learn more beginner tutorials on NixOS, check out <a target="_blank" href="https://medium.com/thenixos">the NixOS</a> publication on Medium.</p>
<ul>
<li><p><a target="_blank" href="https://medium.com/thenixos/what-is-declarative-configuration-in-nixos-understanding-declarative-vs-imperative-approaches-d24d4d144df6">What is Declarative Configuration in NixOS? Understanding Declarative vs Imperative Approaches</a></p>
</li>
<li><p><a target="_blank" href="https://medium.com/thenixos/understand-the-difference-between-home-manager-vs-nix-flake-in-nixos-0511dc8c1a93"><strong>Understand the difference between Home Manager vs Nix Flake in NixOS?</strong></a></p>
</li>
<li><p><a target="_blank" href="https://medium.com/thenixos/why-did-i-choose-nixos-and-what-are-the-advantages-and-disadvantages-of-using-nixos-afaaf95f7d8e">Why did I choose NixOS, and what are the advantages and disadvantages of using NixOS?</a></p>
</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Set Up GitHub CLI on WSL2 ]]>
                </title>
                <description>
                    <![CDATA[ Recently, I set up WSL2 and Ubuntu on my Windows 11 to work on some open-source projects. Since I also maintain these projects, I installed GitHub CLI to ease my workflow. I successfully installed the GitHub CLI, but failed to authenticate it. The er... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/github-cli-wsl2-guide/</link>
                <guid isPermaLink="false">689e444cbfe79386885372b0</guid>
                
                    <category>
                        <![CDATA[ GitHub ]]>
                    </category>
                
                    <category>
                        <![CDATA[ WSL ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Linux ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Ayu Adiati ]]>
                </dc:creator>
                <pubDate>Thu, 14 Aug 2025 20:17:16 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1755202477019/fbc68131-107a-40ae-9dae-c14224d0866a.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Recently, I set up WSL2 and Ubuntu on my Windows 11 to work on some open-source projects. Since I also maintain these projects, I installed <a target="_blank" href="https://cli.github.com/">GitHub CLI</a> to ease my workflow. I successfully installed the GitHub CLI, but failed to authenticate it.</p>
<p>The error message <code>failed to authenticate via web browser: Too many requests have been made in the same timeframe. (slow_down)</code> appeared on my terminal, while on the web browser, it indicated that the authentication was successful.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754718774837/0d1de969-a1e3-4f0a-a3ce-e3c4661ce0d0.png" alt="A message says &quot;Congratulations, you're all set,&quot; marking GitHub CLI authentication is successful " class="image--center mx-auto" width="457" height="334" loading="lazy"></p>
<p>I googled and found some workarounds that I tried, but only one worked like a charm!</p>
<p>After finally solving the tricky authentication issue for GitHub CLI on WSL2, I've put together this guide. It's a complete walkthrough for a solution that works, covering everything from a smooth installation to ongoing management.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-install-github-cli-on-wsl2">How to Install GitHub CLI on WSL2</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-authenticate-github-cli-on-wsl2-with-your-github-account">How to Authenticate GitHub CLI on WSL2 with Your GitHub Account</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-upgrade-github-cli-on-wsl2">How to Upgrade GitHub CLI on WSL2</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-uninstall-github-cli-on-wsl2">How to Uninstall GitHub CLI on WSL2</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-revoke-github-cli-access-on-github">How to Revoke GitHub CLI Access on GitHub</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-final-words">Final Words</a></p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before getting started, ensure that you have these installed on your Windows machine:</p>
<ul>
<li><p>WSL2</p>
</li>
<li><p>A Linux distro</p>
</li>
<li><p>Windows PowerShell</p>
</li>
<li><p><a target="_blank" href="https://learn.microsoft.com/en-us/windows/terminal/install">Windows Terminal</a> (optional)</p>
</li>
</ul>
<p>To follow the instructions in this article, you can use Windows PowerShell terminal as an administrator.</p>
<p>Alternatively, if you have Windows Terminal installed, you can use the Linux terminal by clicking the ‘down arrow’ icon at the top and selecting the distro.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754677301223/7e846117-3fd1-42a2-ab3e-029e94672aca.png" alt="Dropdown menu at Windows Terminal" class="image--center mx-auto" width="582" height="375" loading="lazy"></p>
<h2 id="heading-how-to-install-github-cli-on-wsl2">How to Install GitHub CLI on WSL2</h2>
<p>You can use the installation process described here if you use Ubuntu, Debian, or Raspberry Pi OS (apt) distros. For other distros other than those mentioned here, you can walk through the installation process that's available on the <a target="_blank" href="https://github.com/cli/cli/blob/trunk/docs/install_linux.md">GitHub CLI official docs</a>.</p>
<p>To install GitHub CLI in WSL2:</p>
<ol>
<li><p>Run this command:</p>
<pre><code class="lang-bash"> (<span class="hljs-built_in">type</span> -p wget &gt;/dev/null || (sudo apt update &amp;&amp; sudo apt install wget -y)) \
     &amp;&amp; sudo mkdir -p -m 755 /etc/apt/keyrings \
     &amp;&amp; out=$(mktemp) &amp;&amp; wget -nv -O<span class="hljs-variable">$out</span> https://cli.github.com/packages/githubcli-archive-keyring.gpg \
     &amp;&amp; cat <span class="hljs-variable">$out</span> | sudo tee /etc/apt/keyrings/githubcli-archive-keyring.gpg &gt; /dev/null \
     &amp;&amp; sudo chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg \
     &amp;&amp; sudo mkdir -p -m 755 /etc/apt/sources.list.d \
     &amp;&amp; <span class="hljs-built_in">echo</span> <span class="hljs-string">"deb [arch=<span class="hljs-subst">$(dpkg --print-architecture)</span> signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main"</span> | sudo tee /etc/apt/sources.list.d/github-cli.list &gt; /dev/null \
     &amp;&amp; sudo apt update \
     &amp;&amp; sudo apt install gh -y
</code></pre>
</li>
<li><p>Type your Linux password when you get prompted.</p>
</li>
<li><p>Ensure the GitHub CLI is installed by running <code>gh --version</code> command. If the installation is successful, you should see something like this in your terminal:</p>
<pre><code class="lang-bash"> gh version 2.76.2 (2025-07-30)
 https://github.com/cli/cli/releases/tag/v2.76.2
</code></pre>
</li>
</ol>
<h2 id="heading-how-to-authenticate-github-cli-on-wsl2-with-your-github-account">How to Authenticate GitHub CLI on WSL2 with Your GitHub Account</h2>
<p>Before you can use GitHub CLI, you must first authenticate it. You will get an <code>HTTP 401: Bad credentials (https://api.github.com/graphql)</code> error message if you run any GitHub CLI command without authenticating.</p>
<p>To authenticate GitHub CLI with your GitHub account:</p>
<ol>
<li><p>Run the <code>gh auth login</code> command in your terminal.</p>
</li>
<li><p>You will receive several prompts, and you need to choose the methods you prefer. Here’s what I selected in each prompt:</p>
<pre><code class="lang-plaintext"> ? Where do you use GitHub? GitHub.com
 ? What is your preferred protocol for Git operations on this host? HTTPS
 ? How would you like to authenticate GitHub CLI? Login with a web browser
</code></pre>
<p> After answering all prompts, you should get the message to copy a one-time code as shown below. You <strong>don’t need to copy the code</strong> at this point.</p>
<pre><code class="lang-bash"> ! First copy your one-time code: XXXX—XXXX
</code></pre>
</li>
<li><p>Press ‘Enter’. It automatically opens the "Device Activation" page on your browser.</p>
</li>
<li><p>Click the green ‘Continue’ button.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754848322666/2a4af9ab-c197-4ec9-802f-ad9b4f24375c.png" alt="GitHub Device Activation page on a browser" class="image--center mx-auto" width="486" height="384" loading="lazy"></p>
<p> GitHub should ask you to enter the code displayed on your terminal, as shown in the screenshot below. But here’s the trick! <strong>Don’t paste any code, and don’t close the browser</strong>. Let’s first get back to your terminal.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754722491767/d84534da-522f-4e82-84c2-a1bfc75940ef.png" alt="GitHub Device Activation page on a browser" class="image--center mx-auto" width="436" height="460" loading="lazy"></p>
<p> Now you might get this error message on your terminal:</p>
<pre><code class="lang-bash"> grep: /proc/sys/fs/binfmt_misc/WSLInterop: No such file or directory
 WSL Interopability is disabled. Please <span class="hljs-built_in">enable</span> it before using WSL.
 grep: /proc/sys/fs/binfmt_misc/WSLInterop: No such file or directory
 [error] WSL Interoperability is disabled. Please <span class="hljs-built_in">enable</span> it before using WSL.
</code></pre>
</li>
<li><p>Press <code>Ctrl + C</code> to stop the process if it's still running, or let it stop by itself. Once it's stopped, you should see this message:</p>
<pre><code class="lang-bash"> failed to authenticate via web browser: Too many requests have been made <span class="hljs-keyword">in</span> the same timeframe. (slow_down)
</code></pre>
</li>
<li><p>Run the <code>gh auth login</code> command again and repeat the process to select the methods of your choice. This time, when it asks you to press ‘Enter’, <strong>don’t press it</strong>.</p>
</li>
<li><p>Copy the latest code and return to the "Device Activation" page that you left open in your browser.</p>
</li>
<li><p>Paste the code that you copied and click the green ‘Continue’ button.</p>
</li>
<li><p>Click the green ‘Authorize github’ button after GitHub redirects you to the “Authorize GitHub CLI” page. You should now see the message “Congratulations, you're all set!”</p>
</li>
<li><p>Get back to your terminal and press ‘Enter’. Doing so triggers these actions:</p>
<ul>
<li><p>It automatically opens a new “Device Activation” page in your browser. You can safely ignore this.</p>
</li>
<li><p>In the terminal, you first see the error message as in step 4. Don’t do anything and wait for a little bit. Then, you get:</p>
<pre><code class="lang-bash">  ✓ Authentication complete.
  - gh config <span class="hljs-built_in">set</span> -h github.com git_protocol https
  ✓ Configured git protocol
  ! Authentication credentials saved <span class="hljs-keyword">in</span> plain text
  ✓ Logged <span class="hljs-keyword">in</span> as YOUR-GITHUB-USERNAME
  ! You were already logged <span class="hljs-keyword">in</span> to this account
</code></pre>
</li>
</ul>
</li>
</ol>
<p>And GitHub CLI is now successfully authenticated!</p>
<blockquote>
<p>Credit goes to <a target="_blank" href="https://github.com/cli/cli/discussions/6884#discussioncomment-10176332">username “ikeyan” on GitHub for their GitHub CLI authentication solution</a>!</p>
</blockquote>
<h2 id="heading-how-to-upgrade-github-cli-on-wsl2">How to Upgrade GitHub CLI on WSL2</h2>
<p>It’s always a good practice to regularly check for package and dependency updates, and upgrade to the newest version when it’s available — this includes GitHub CLI. To check for updates and upgrade the version of GitHub CLI:</p>
<ol>
<li><p>Run the <code>sudo apt update</code> command in your terminal. This command fetches the list of available updates.</p>
</li>
<li><p>Type your Linux password when you get prompted.</p>
</li>
<li><p>If you need to upgrade your GitHub CLI, run <code>sudo apt install gh</code>. This command installs the newest version of GitHub CLI.</p>
</li>
<li><p>Type your Linux password when you get prompted.</p>
</li>
</ol>
<p>Now your GitHub CLI has the newest version.</p>
<h2 id="heading-how-to-uninstall-github-cli-on-wsl2">How to Uninstall GitHub CLI on WSL2</h2>
<p>If one day you feel like you don’t need to use GitHub CLI anymore, you can uninstall it by following these steps:</p>
<ol>
<li><p>Run the <code>sudo apt remove gh</code> command in your terminal.</p>
</li>
<li><p>Type your Linux password when you get prompted.</p>
</li>
<li><p>Press ‘Y’ to continue the uninstall process.</p>
</li>
</ol>
<p>GitHub CLI is now uninstalled from your WSL environment.</p>
<h2 id="heading-how-to-revoke-github-cli-access-on-github">How to Revoke GitHub CLI Access on GitHub</h2>
<p>After uninstalling the GitHub CLI, you might think your account access is gone, but it's not. The authentication you granted is still active. If you don't plan on using the CLI again, it's a good practice to revoke this access.</p>
<p>Here's how to do it directly from your GitHub account:</p>
<ol>
<li><p>On your GitHub account, click your profile picture on the top right and click ‘Settings’.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754725091482/8fb8a0fd-8dbd-4342-9fe8-309a13d72c39.png" alt="Settings option on dropdown menu at GitHub" class="image--center mx-auto" width="283" height="471" loading="lazy"></p>
<ol start="2">
<li><p>On the left side bar, find ‘Integrations’ and click ‘Applications’.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754815240842/ca49d207-6ee2-476f-a53d-bde53b2d57dd.png" alt="Applications tab in the Integrations settings on GitHub" class="image--center mx-auto" width="425" height="158" loading="lazy"></p>
</li>
<li><p>Click the ‘Authorized OAuth Apps’ tab on top.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754815346304/a360f7dc-7024-44c3-8e19-15d94b35ce8e.png" alt="Authorized OAuth Apps tab on GitHub" class="image--center mx-auto" width="837" height="141" loading="lazy"></p>
</li>
<li><p>Find GitHub CLI and click the ‘three dots’ icon next to it.</p>
</li>
<li><p>Click ‘Revoke’.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754725454783/dd544380-482a-4385-97c1-4ebc35026658.png" alt="Revoke option on GitHub to revoke an authorized OAuth apps" class="image--center mx-auto" width="1369" height="164" loading="lazy"></p>
</li>
<li><p>Confirm it by clicking the ‘I understand, revoke access’ button.</p>
</li>
</ol>
</li>
</ol>
<p>Now, GitHub CLI doesn’t have access to your GitHub account.</p>
<hr>
<h2 id="heading-final-words">Final Words</h2>
<p>🖼️ Credit cover image: <a target="_blank" href="http://undraw.co">undraw.co</a></p>
<p>Thank you for reading! Last, you can find me on <a target="_blank" href="https://twitter.com/@AdiatiAyu">X</a> and <a target="_blank" href="https://www.linkedin.com/in/adiatiayu/">LinkedIn</a>. Let's connect! 😊</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Schedule Tasks in Red Hat Enterprise Linux ]]>
                </title>
                <description>
                    <![CDATA[ Red Hat Enterprise Linux (RHEL) is a leading enterprise-grade Linux distribution widely regarded as the gold standard for mission-critical server environments. It provides robust, secure, and scalable solutions for organizations ranging from small bu... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-schedule-tasks-in-red-hat-enterprise-linux/</link>
                <guid isPermaLink="false">685c9989f2073d62fe9b82f5</guid>
                
                    <category>
                        <![CDATA[ RHEL ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Linux ]]>
                    </category>
                
                    <category>
                        <![CDATA[ rhcsa ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Hang Hu ]]>
                </dc:creator>
                <pubDate>Thu, 26 Jun 2025 00:51:21 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1750869114329/79072c41-988a-41f2-9e2f-25618d78fefc.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Red Hat Enterprise Linux (RHEL) is a leading enterprise-grade Linux distribution widely regarded as the gold standard for mission-critical server environments. It provides robust, secure, and scalable solutions for organizations ranging from small businesses to Fortune 500 companies, powering everything from web servers and databases to cloud infrastructure and containerized applications.</p>
<p>You can use RHEL's task scheduling capabilities in scenarios like automating system maintenance (for example, log rotation or backup operations), managing routine administrative tasks (like user account cleanup or security updates), or orchestrating complex workflows in enterprise environments. These scheduling tools are essential for maintaining system health and ensuring that critical operations run without manual intervention.</p>
<p>For system administrators, think of task scheduling as the backbone of automated system management, enabling you to set up processes that run reliably in the background while you focus on more strategic initiatives. Its power lies in its flexibility and reliability, making it an indispensable skill for anyone managing Linux systems in production environments.</p>
<p>In this tutorial, you’ll learn how to schedule tasks in Red Hat Enterprise Linux using various built-in tools and techniques. This content is part of <strong>Schedule Future Tasks</strong>, which is Chapter 2 of the <a target="_blank" href="https://labex.io/courses/red-hat-system-administration-rh134-labs">Red Hat System Administration (RH134) course</a>. RH134 is a fundamental course for the Red Hat Certified System Administrator (RHCSA) certification, one of the most respected credentials in the Linux administration field.</p>
<p>This hands-on tutorial provides practical experience with the scheduling concepts covered in the RH134 curriculum, giving you the skills needed to automate tasks effectively in enterprise RHEL environments.</p>
<h3 id="heading-heres-what-well-cover">Here's what we'll cover:</h3>
<ul>
<li><p><a class="post-section-overview" href="#heading-how-to-schedule-a-one-time-job-with-at">How to Schedule a One-time Job with 'at'</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-manage-at-jobs">How to Manage 'at' jobs</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-schedule-recurring-user-jobs-with-crontab">How to Schedule Recurring User Jobs with 'crontab'</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-manage-user-crontab-entries">How to Manage User 'crontab' Entries</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-schedule-recurring-system-jobs-with-cron-directories">How to Schedule Recurring System Jobs with cron Directories</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-configure-systemd-timers-for-recurring-tasks">How to Configure systemd Timers for Recurring Tasks</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-manage-temporary-files-with-systemd-tmpfiles">How to Manage Temporary Files with systemd-tmpfiles</a></p>
</li>
</ul>
<h2 id="heading-prerequisites"><strong>Prerequisites</strong></h2>
<p>This tutorial is designed to be beginner-friendly! You just need basic familiarity with using the Linux command line. If you can navigate directories and run simple commands, you're ready to start.</p>
<p>For those looking to deepen their RHEL knowledge, the <a target="_blank" href="https://labex.io/skilltrees/rhel">RHEL Skill Tree</a> offers comprehensive hands-on labs including <a target="_blank" href="https://labex.io/courses/red-hat-system-administration-rh124-labs">RH124</a>, <a target="_blank" href="https://labex.io/courses/red-hat-system-administration-rh134-labs">RH134</a>, <a target="_blank" href="https://labex.io/courses/red-hat-enterprise-linux-automation-with-ansible-rh294">RH294</a>, and other courses for RHCSA and RHCE certifications.</p>
<p>Don't worry if you're new to Red Hat Enterprise Linux – I'll explain everything step by step, and these concepts work on most Linux distributions too.</p>
<h2 id="heading-how-to-schedule-a-one-time-job-with-at"><strong>How to Schedule a One-time Job with 'at'</strong></h2>
<p>First, let’s learn how to schedule a job to run once at a future time using the <code>at</code> command. The <code>at</code> command is useful for executing commands that don’t need to be run repeatedly. We will schedule a simple job, inspect its details, and then remove it.</p>
<p>In this tutorial, we will work directly on the local system to learn task scheduling. You’ll execute all commands in your current terminal environment.</p>
<p>Let's schedule a job to print the current date and time into a file named <code>~/myjob.txt</code> in your home directory. We'll schedule it to run 3 minutes from now:</p>
<pre><code class="lang-bash">at now + 3 minutes &lt;&lt; EOF
date &gt; ~/myjob.txt
EOF
</code></pre>
<p>The <code>warning: commands will be executed using /bin/sh</code> message is normal. The <code>job N at ...</code> output indicates the job number and the scheduled execution time. Make a note of the job number, as you will need it later.</p>
<p>Next, let's schedule another job interactively. This method is useful for entering multiple commands or more complex scripts. We will schedule a job to append "Hello from at job!" to <code>~/at_output.txt</code> 5 minutes from now:</p>
<pre><code class="lang-bash">at now + 5 minutes
</code></pre>
<p>After typing the command and pressing Enter, you will see an <code>at&gt;</code> prompt. Type your command and then press <code>Ctrl+d</code> to finish:</p>
<pre><code class="lang-bash">at &gt; <span class="hljs-built_in">echo</span> <span class="hljs-string">"Hello from at job!"</span> &gt;&gt; ~/at_output.txt
at &gt; Ctrl+d
</code></pre>
<p>To view the jobs currently in the <code>at</code> queue, use the <code>atq</code> command. This command lists all pending <code>at</code> jobs for the current user.</p>
<pre><code class="lang-bash">atq
</code></pre>
<p>The output will show the job number, the scheduled time, the queue, and the user who scheduled it.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750726190789/d2dd54c0-80a0-4bb2-8561-3114bb279387.png" alt="Output of atq command showing scheduled jobs" class="image--center mx-auto" width="672" height="333" loading="lazy"></p>
<p>You can inspect the commands that a specific <code>at</code> job will run using the <code>at -c</code> command followed by the job number. Replace <code>N</code> with one of the job numbers you noted earlier.</p>
<pre><code class="lang-bash">at -c N
</code></pre>
<p>This command will display the shell script that <code>at</code> will execute for that job. You should see the <code>date &gt; ~/myjob.txt</code> or <code>echo "Hello from at job!" &gt;&gt; ~/at_output.txt</code> command within the output.</p>
<p>Finally, to remove a scheduled <code>at</code> job, use the <code>atrm</code> command followed by the job number. Let's remove the first job we scheduled. Replace <code>N</code> with the job number of your first job.</p>
<pre><code class="lang-bash">atrm N
</code></pre>
<p>After removing the job, you can use <code>atq</code> again to verify that it is no longer in the queue.</p>
<pre><code class="lang-bash">atq
</code></pre>
<p>You should now only see the second job (if it hasn't executed yet) or an empty queue if both jobs have been removed or executed.</p>
<p>This completes the first step of scheduling one-time jobs with the <code>at</code> command.</p>
<h2 id="heading-how-to-manage-at-jobs"><strong>How to Manage 'at' jobs</strong></h2>
<p>Now, let’s delve deeper into managing <code>at</code> jobs, including scheduling jobs with different queues and verifying their execution. Understanding <code>at</code> queues can be useful for prioritizing tasks or separating different types of one-time jobs.</p>
<p>We will continue working on the local system to explore more advanced <code>at</code> job management features.</p>
<p>The <code>at</code> command allows you to specify a queue using the <code>-q</code> option. Queues are single letters from <code>a</code> to <code>z</code>. Queue <code>a</code> is the default, and jobs in queues <code>a</code> through <code>z</code> are executed with decreasing niceness (priority). Queue <code>a</code> has the highest priority, and queue <code>z</code> has the lowest. Queue <code>b</code> is reserved for batch jobs.</p>
<p>Let's schedule a job in queue <code>g</code> (a lower priority queue) to run in 2 minutes. This job will create a file named <code>~/queue_g_job.txt</code> with a timestamp:</p>
<pre><code class="lang-bash">at -q g now + 2 minutes &lt;&lt; EOF
date &gt; ~/queue_g_job.txt
EOF
</code></pre>
<p>You will see output similar to <code>job N at ...</code>. Note down this job number.</p>
<p>Next, let's schedule another job, this time in queue <code>b</code> (batch queue), which is typically used for jobs that can run when system load is low. This job will append "Batch job executed!" to <code>~/batch_job.txt</code>. We'll schedule it to run 4 minutes from now:</p>
<pre><code class="lang-bash">at -q b now + 4 minutes &lt;&lt; EOF
<span class="hljs-built_in">echo</span> <span class="hljs-string">"Batch job executed!"</span> &gt;&gt; ~/batch_job.txt
EOF
</code></pre>
<p>Again, note down the job number.</p>
<p>To see all pending jobs, including those in different queues, use <code>atq</code>.</p>
<pre><code class="lang-bash">atq
</code></pre>
<p>You should now see both jobs listed, with their respective queue letters (<code>g</code> and <code>b</code>).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750726218380/bcc9d551-0530-48d1-bf7f-46073c6f77a6.png" alt="Output of atq command showing scheduled jobs" class="image--center mx-auto" width="589" height="325" loading="lazy"></p>
<p>Now, wait for your scheduled jobs to execute. Wait for at least 5 minutes to allow all jobs to complete. You can check if the files created by your <code>at</code> jobs exist and contain the expected content.</p>
<p>Check <code>~/queue_g_job.txt</code>:</p>
<pre><code class="lang-bash">cat ~/queue_g_job.txt
</code></pre>
<p>You should see a date and time string.</p>
<p>Check <code>~/batch_job.txt</code>:</p>
<pre><code class="lang-bash">cat ~/batch_job.txt
</code></pre>
<p>You should see "Batch job executed!".</p>
<p>If the files are not present or empty, it might mean the jobs haven't executed yet, or there was an issue with the command. You can re-check <code>atq</code> to see if they are still pending.</p>
<h2 id="heading-how-to-schedule-recurring-user-jobs-with-crontab"><strong>How to Schedule Recurring User Jobs with 'crontab'</strong></h2>
<p>Next, you’ll learn how to schedule recurring tasks for a specific user using <code>crontab</code>. Unlike <code>at</code> jobs, which run once, <code>cron</code> jobs run repeatedly at specified intervals. This is ideal for routine maintenance, data backups, or generating reports.</p>
<p>We will continue working on the local system to learn about user crontab management.</p>
<p>The <code>crontab</code> command allows users to create, edit, and view their own <code>cron</code> jobs. Each user has their own <code>crontab</code> file.</p>
<p>To edit your <code>crontab</code> file, use the <code>crontab -e</code> command. This will open your <code>crontab</code> file in the default text editor (usually <code>vim</code>).</p>
<pre><code class="lang-bash">crontab -e
</code></pre>
<p><strong>Vim editor instructions:</strong></p>
<ul>
<li><p>Press <code>i</code> to enter insert mode (you'll see <code>-- INSERT --</code> at the bottom)</p>
</li>
<li><p>Use arrow keys to navigate</p>
</li>
<li><p>To save and exit: Press <code>Esc</code> to exit insert mode, then type <code>:wq</code> and press <code>Enter</code></p>
</li>
<li><p>To exit without saving: Press <code>Esc</code>, then type <code>:q!</code> and press <code>Enter</code></p>
</li>
</ul>
<p>Inside the editor, you will add a new line to define your <code>cron</code> job. A <code>cron</code> entry has five time-and-date fields, followed by the command to be executed. The fields are:</p>
<ul>
<li><p><strong>Minute (0-59)</strong></p>
</li>
<li><p><strong>Hour (0-23)</strong></p>
</li>
<li><p><strong>Day of Month (1-31)</strong></p>
</li>
<li><p><strong>Month (1-12)</strong></p>
</li>
<li><p><strong>Day of Week (0-7, where 0 or 7 is Sunday)</strong></p>
</li>
</ul>
<p>You can use <code>*</code> as a wildcard to mean "every" for a field, or <code>/</code> to specify step values (for example, <code>*/5</code> for every 5 minutes).</p>
<p>Let's schedule a job that appends the current date and time to a file named <code>~/my_cron_log.txt</code> every minute. This will allow us to quickly observe the <code>cron</code> job in action.</p>
<p>Follow these steps in Vim:</p>
<ol>
<li><p>Press <code>i</code> to enter insert mode</p>
</li>
<li><p>Add the following line to the <code>crontab</code> file:</p>
</li>
</ol>
<pre><code class="lang-bash">* * * * * /usr/bin/date &gt;&gt; ~/my_cron_log.txt
</code></pre>
<ol start="3">
<li><p>Press <code>Esc</code> to exit insert mode</p>
</li>
<li><p>Type <code>:wq</code> and press <code>Enter</code> to save and exit</p>
</li>
</ol>
<p>You should see a message indicating that a new <code>crontab</code> has been installed:</p>
<pre><code class="lang-plaintext">crontab: installing new crontab
</code></pre>
<p>To verify that your <code>cron</code> job has been successfully added, you can list your <code>crontab</code> entries using the <code>crontab -l</code> command:</p>
<pre><code class="lang-bash">crontab -l
</code></pre>
<p>You should see the line you just added:</p>
<pre><code class="lang-plaintext">* * * * * /usr/bin/date &gt;&gt; ~/my_cron_log.txt
</code></pre>
<p>Now, wait for a minute or two to allow the <code>cron</code> job to execute at least once. You can check the current time to see when the next minute mark will occur:</p>
<pre><code class="lang-bash">date
</code></pre>
<p>After waiting for at least two minutes to allow the cron job to execute a couple of times, check the content of the <code>~/my_cron_log.txt</code> file.</p>
<pre><code class="lang-bash">cat ~/my_cron_log.txt
</code></pre>
<p>You should see one or more lines, each containing a date and time, indicating that your <code>cron</code> job has executed.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750726409656/bfd85cf0-316a-4c1d-89c2-0d60c30cd33f.png" alt="Cron job output in log file" class="image--center mx-auto" width="607" height="281" loading="lazy"></p>
<pre><code class="lang-plaintext">Mon Apr 8 10:30:01 AM EDT 2025
Mon Apr 8 10:31:01 AM EDT 2025
</code></pre>
<h2 id="heading-how-to-manage-user-crontab-entries"><strong>How to Manage User 'crontab' Entries</strong></h2>
<p>Now you will learn more advanced techniques for managing user <code>crontab</code> entries, including editing existing jobs, adding multiple jobs, and understanding special <code>cron</code> strings. Effective <code>crontab</code> management is crucial for automating routine tasks.</p>
<p>We will continue working on the local system to explore advanced crontab management techniques.</p>
<p>Let's start by adding a new <code>cron</code> job. This job will append "Hello from cron!" to <code>~/cron_messages.txt</code> every two minutes.</p>
<p>Open your <code>crontab</code> for editing:</p>
<pre><code class="lang-bash">crontab -e
</code></pre>
<p>In Vim:</p>
<ol>
<li><p>Press <code>i</code> to enter insert mode</p>
</li>
<li><p>Add the following line to the <code>crontab</code> file:</p>
</li>
</ol>
<pre><code class="lang-bash">*/2 * * * * <span class="hljs-built_in">echo</span> <span class="hljs-string">"Hello from cron!"</span> &gt;&gt; ~/cron_messages.txt
</code></pre>
<ol start="3">
<li><p>Press <code>Esc</code> to exit insert mode</p>
</li>
<li><p>Type <code>:wq</code> and press <code>Enter</code> to save and exit</p>
</li>
</ol>
<p>Verify that the entry is added:</p>
<pre><code class="lang-bash">crontab -l
</code></pre>
<p>You should see the newly added line.</p>
<p>Now, let's add another <code>cron</code> job that runs daily at 08:00 AM. This job will record the disk usage of your home directory to <code>~/disk_usage.log</code>.</p>
<p>Open your <code>crontab</code> for editing again:</p>
<pre><code class="lang-bash">crontab -e
</code></pre>
<p>In Vim:</p>
<ol>
<li><p>Press <code>i</code> to enter insert mode</p>
</li>
<li><p>Add the following line below the previous one:</p>
</li>
</ol>
<pre><code class="lang-bash">0 8 * * * du -sh ~ &gt;&gt; ~/disk_usage.log
</code></pre>
<ol start="3">
<li><p>Press <code>Esc</code> to exit insert mode</p>
</li>
<li><p>Type <code>:wq</code> and press <code>Enter</code> to save and exit</p>
</li>
</ol>
<p>Verify that both entries are present:</p>
<pre><code class="lang-bash">crontab -l
</code></pre>
<p>You should now see both <code>cron</code> jobs listed.</p>
<p><code>cron</code> also supports special strings that can simplify common schedules. These include <code>@reboot</code>, <code>@yearly</code>, <code>@annually</code>, <code>@monthly</code>, <code>@weekly</code>, <code>@daily</code>, <code>@midnight</code>, and <code>@hourly</code>. For example, <code>@hourly</code> is equivalent to <code>0 * * * *</code>.</p>
<p>Let's add a job that runs hourly and records the system uptime to <code>~/uptime_log.txt</code>.</p>
<p>Open your <code>crontab</code> for editing:</p>
<pre><code class="lang-bash">crontab -e
</code></pre>
<p>In Vim:</p>
<ol>
<li><p>Press <code>i</code> to enter insert mode</p>
</li>
<li><p>Add the following line:</p>
</li>
</ol>
<pre><code class="lang-bash">@hourly uptime &gt;&gt; ~/uptime_log.txt
</code></pre>
<ol start="3">
<li><p>Press <code>Esc</code> to exit insert mode</p>
</li>
<li><p>Type <code>:wq</code> and press <code>Enter</code> to save and exit</p>
</li>
</ol>
<p>Verify all three entries:</p>
<pre><code class="lang-bash">crontab -l
</code></pre>
<p>You should now see all three <code>cron</code> jobs.</p>
<p>To demonstrate the effect of these jobs, we will wait for a short period. Since the jobs are scheduled at different intervals, we won't see all of them execute immediately, but we can verify the setup.</p>
<p>Wait for at least 3 minutes to allow the <code>*/2</code> job to run at least once.</p>
<p>Check the <code>~/cron_messages.txt</code> file:</p>
<pre><code class="lang-bash">cat ~/cron_messages.txt
</code></pre>
<p>You should see at least one "Hello from cron!" message.</p>
<pre><code class="lang-plaintext">Hello from cron!
</code></pre>
<p>The <code>~/disk_usage.log</code> and <code>~/uptime_log.txt</code> files might not be created yet, depending on the current time, as they are scheduled for daily and hourly execution, respectively. The important part is that their entries are correctly configured in your <code>crontab</code>.</p>
<h2 id="heading-how-to-schedule-recurring-system-jobs-with-cron-directories"><strong>How to Schedule Recurring System Jobs with</strong> <code>cron</code> <strong>Directories</strong></h2>
<p>In this step, you will learn how to schedule recurring system-wide tasks using <code>cron</code> directories. Unlike user <code>crontab</code> entries, which are specific to a user, system <code>cron</code> jobs are managed by the root user and affect the entire system. These are typically used for system maintenance, log rotation, and other administrative tasks.</p>
<p>We will continue working on the local system to explore system-wide cron job configuration.</p>
<p>System-wide <code>cron</code> jobs are defined in <code>/etc/crontab</code> or by placing scripts in specific directories:</p>
<ul>
<li><p><code>/etc/cron.hourly/</code>: Scripts in this directory run once an hour.</p>
</li>
<li><p><code>/etc/cron.daily/</code>: Scripts in this directory run once a day.</p>
</li>
<li><p><code>/etc/cron.weekly/</code>: Scripts in this directory run once a week.</p>
</li>
<li><p><code>/etc/cron.monthly/</code>: Scripts in this directory run once a month.</p>
</li>
</ul>
<p>These directories are processed by the <code>run-parts</code> utility, which executes all executable files within them.</p>
<p>To manage system <code>cron</code> jobs, you need root privileges. Since the labex user has sudo access, we can use <code>sudo</code> for the required commands.</p>
<p>Let's create a simple script that logs a message to the system log. We will place this script in <code>/etc/cron.hourly/</code> to make it run hourly.</p>
<p>First, create the script file <code>/etc/cron.hourly/my_hourly_script</code>:</p>
<pre><code class="lang-bash">sudo nano /etc/cron.hourly/my_hourly_script
</code></pre>
<p>Add the following content to the file:</p>
<pre><code class="lang-bash"><span class="hljs-meta">#!/bin/bash</span>
logger <span class="hljs-string">"Hourly cron job executed at <span class="hljs-subst">$(date)</span>"</span>
</code></pre>
<p>Save and exit the editor (<code>Ctrl+o</code>, <code>Enter</code>, <code>Ctrl+x</code> in <code>nano</code>).</p>
<p>Next, you need to make the script executable. Without execute permissions, <code>run-parts</code> will ignore it.</p>
<pre><code class="lang-bash">sudo chmod +x /etc/cron.hourly/my_hourly_script
</code></pre>
<p>Now, let's verify that the script is executable:</p>
<pre><code class="lang-bash">ls -l /etc/cron.hourly/my_hourly_script
</code></pre>
<p>You should see <code>x</code> in the permissions, for example: <code>-rwxr-xr-x</code>.</p>
<p>Since <code>cron.hourly</code> jobs run once an hour, we can't wait for a full hour to verify its execution in this tutorial. But we can manually trigger the <code>run-parts</code> command for the hourly directory to simulate its execution.</p>
<pre><code class="lang-bash">sudo run-parts /etc/cron.hourly/
</code></pre>
<p>This command will execute all executable scripts in <code>/etc/cron.hourly/</code>. The script we created uses the <code>logger</code> command to write messages to the system log.</p>
<p>In a real RHEL system, you would be able to check the system logs using <code>journalctl</code> or <code>/var/log/messages</code> to verify that the script executed successfully.</p>
<p>This completes the system cron job management step. The script will remain in place and would execute hourly in a real system environment.</p>
<h2 id="heading-how-to-configure-systemd-timers-for-recurring-tasks"><strong>How to Configure</strong> <code>systemd</code> <strong>Timers for Recurring Tasks</strong></h2>
<p>Next, you will learn about <code>systemd</code> timers, which are a modern alternative to <code>cron</code> for scheduling tasks on Linux systems. <code>systemd</code> timers offer more flexibility and better integration with the <code>systemd</code> ecosystem.</p>
<p><code>systemd</code> timers work in conjunction with <code>systemd</code> service units. A timer unit (<code>.timer</code> file) defines when a task should run, and a service unit (<code>.service</code> file) defines what task should be executed.</p>
<p>We will continue working on the local system to explore systemd timer configuration.</p>
<p>You will need root privileges to create <code>systemd</code> unit files in system directories. Since the labex user has sudo access, we can use <code>sudo</code> for the required commands.</p>
<p>Let's create a simple service that logs a message to a file. We will place this service unit file in <code>/etc/systemd/system/</code> which is where custom service units are typically stored.</p>
<p>Create the service unit file <code>/etc/systemd/system/my-custom-task.service</code>:</p>
<pre><code class="lang-bash">sudo nano /etc/systemd/system/my-custom-task.service
</code></pre>
<p>Add the following content to the file:</p>
<pre><code class="lang-ini"><span class="hljs-section">[Unit]</span>
<span class="hljs-attr">Description</span>=My Custom Scheduled Task

<span class="hljs-section">[Service]</span>
<span class="hljs-attr">Type</span>=<span class="hljs-literal">on</span>eshot
<span class="hljs-attr">ExecStart</span>=/bin/bash -c <span class="hljs-string">'echo "My custom task executed at $(date)" &gt;&gt; /var/log/my-custom-task.log'</span>
</code></pre>
<p>Save and exit the editor (<code>Ctrl+o</code>, <code>Enter</code>, <code>Ctrl+x</code> in <code>nano</code>).</p>
<p>Next, create the timer unit file <code>/etc/systemd/system/my-custom-task.timer</code>. This timer will activate our service every 5 minutes.</p>
<pre><code class="lang-bash">sudo nano /etc/systemd/system/my-custom-task.timer
</code></pre>
<p>Add the following content to the file:</p>
<pre><code class="lang-ini"><span class="hljs-section">[Unit]</span>
<span class="hljs-attr">Description</span>=Run My Custom Scheduled Task every <span class="hljs-number">5</span> minutes

<span class="hljs-section">[Timer]</span>
<span class="hljs-attr">OnCalendar</span>=*:<span class="hljs-number">0</span>/<span class="hljs-number">5</span>
<span class="hljs-attr">Persistent</span>=<span class="hljs-literal">true</span>

<span class="hljs-section">[Install]</span>
<span class="hljs-attr">WantedBy</span>=timers.target
</code></pre>
<p>Save and exit the editor.</p>
<p><strong>Explanation of</strong> <code>OnCalendar</code>:</p>
<ul>
<li><p><code>*:0/5</code> means "every 5 minutes".</p>
<ul>
<li><p><code>*</code> for year, month, day, hour (any value).</p>
</li>
<li><p><code>0/5</code> for minute, meaning starting at minute 0, every 5 minutes (0, 5, 10, ..., 55).</p>
</li>
</ul>
</li>
</ul>
<p>In a typical <code>systemd</code> environment, you would now run <code>systemctl daemon-reload</code> to make <code>systemd</code> aware of the new unit files, and then <code>systemctl enable --now my-custom-task.timer</code> to start the timer.</p>
<p>Let's verify the existence of the created files:</p>
<pre><code class="lang-bash">ls -l /etc/systemd/system/my-custom-task.service
ls -l /etc/systemd/system/my-custom-task.timer
</code></pre>
<p>You should see output indicating that both files exist.</p>
<p>To simulate the execution of the service, you can manually run the command defined in <code>ExecStart</code>:</p>
<pre><code class="lang-bash">sudo /bin/bash -c <span class="hljs-string">'echo "My custom task executed at $(date)" &gt;&gt; /var/log/my-custom-task.log'</span>
</code></pre>
<p>Now, check the log file to see the output:</p>
<pre><code class="lang-bash">sudo cat /var/<span class="hljs-built_in">log</span>/my-custom-task.log
</code></pre>
<p>You should see the message you just logged:</p>
<pre><code class="lang-plaintext">My custom task executed at Tue Jun 10 06:54:40 UTC 2025
</code></pre>
<p>This completes the systemd timer configuration step. The service and timer unit files will remain in place for reference.</p>
<h2 id="heading-how-to-manage-temporary-files-with-systemd-tmpfiles"><strong>How to Manage Temporary Files with</strong> <code>systemd-tmpfiles</code></h2>
<p>Now you’ll learn how to manage temporary files and directories using <code>systemd-tmpfiles</code>. This utility is part of <code>systemd</code> and is responsible for creating, deleting, and cleaning up volatile and temporary files and directories. It's commonly used to manage <code>/tmp</code>, <code>/var/tmp</code>, and other temporary storage locations, ensuring that old files are removed periodically.</p>
<p>We will continue working on the local system to explore systemd-tmpfiles configuration.</p>
<p>You will need root privileges to configure <code>systemd-tmpfiles</code>. Since the labex user has sudo access, we can use <code>sudo</code> for the required commands.</p>
<p><code>systemd-tmpfiles</code> reads configuration files from <code>/etc/tmpfiles.d/</code> and <code>/usr/lib/tmpfiles.d/</code>. These files define rules for creating, deleting, and managing files and directories.</p>
<p>Let's create a custom configuration file to manage a new temporary directory. We will create a directory <code>/run/my_temp_dir</code> and configure <code>systemd-tmpfiles</code> to clean files older than 1 minute from it.</p>
<p>Create the configuration file <code>/etc/tmpfiles.d/my_temp_dir.conf</code>:</p>
<pre><code class="lang-bash">sudo nano /etc/tmpfiles.d/my_temp_dir.conf
</code></pre>
<p>Add the following content to the file:</p>
<pre><code class="lang-bash">d /run/my_temp_dir 0755 labex labex 1m
</code></pre>
<p><strong>Explanation of the line:</strong></p>
<ul>
<li><p><code>d</code>: Specifies that this entry defines a directory.</p>
</li>
<li><p><code>/run/my_temp_dir</code>: The path to the directory.</p>
</li>
<li><p><code>0755</code>: The permissions for the directory.</p>
</li>
<li><p><code>labex labex</code>: The owner and group for the directory.</p>
</li>
<li><p><code>1m</code>: The age after which files in this directory should be deleted (1 minute).</p>
</li>
</ul>
<p>Save and exit the editor (<code>Ctrl+o</code>, <code>Enter</code>, <code>Ctrl+x</code> in <code>nano</code>).</p>
<p>Now, let's tell <code>systemd-tmpfiles</code> to apply this configuration. The <code>--create</code> option will create the directory if it doesn't exist.</p>
<pre><code class="lang-bash">sudo systemd-tmpfiles --create /etc/tmpfiles.d/my_temp_dir.conf
</code></pre>
<p>Verify that the directory has been created with the correct permissions and ownership:</p>
<pre><code class="lang-bash">ls -ld /run/my_temp_dir
</code></pre>
<p>You should see output similar to:</p>
<pre><code class="lang-plaintext">drwxr-xr-x 2 labex labex 6 Jun 10 06:55 /run/my_temp_dir
</code></pre>
<p>Next, let's create a test file inside this new temporary directory:</p>
<pre><code class="lang-bash">sudo touch /run/my_temp_dir/test_file.txt
</code></pre>
<p>Verify the file exists:</p>
<pre><code class="lang-bash">ls -l /run/my_temp_dir/test_file.txt
</code></pre>
<p>Now, we need to wait for more than 1 minute for the file to become "old" according to our configuration. Wait for at least 70 seconds (1 minute and 10 seconds).</p>
<p>After waiting for more than 1 minute, we will manually run <code>systemd-tmpfiles</code> with the <code>--clean</code> option to trigger the cleanup process based on our configuration.</p>
<pre><code class="lang-bash">sudo systemd-tmpfiles --clean /etc/tmpfiles.d/my_temp_dir.conf
</code></pre>
<p>Finally, check if the <code>test_file.txt</code> has been removed:</p>
<pre><code class="lang-bash">ls -l /run/my_temp_dir/test_file.txt
</code></pre>
<p>You should get a "No such file or directory" error, indicating that <code>systemd-tmpfiles</code> successfully cleaned up the old file.</p>
<p>This completes configuring the systemd-tmpfiles. The configuration file and temporary directory will remain in place for reference.</p>
<h2 id="heading-summary"><strong>Summary</strong></h2>
<p>In this tutorial, you learned how to schedule and manage one-time tasks using the <code>at</code> command, including scheduling jobs interactively and non-interactively, viewing the <code>at</code> queue with <code>atq</code>, and deleting pending jobs with <code>atrm</code>. You also learned how to schedule recurring user-specific tasks using <code>crontab</code>, including how to edit, list, and remove cron jobs, and you learned the cron syntax for specifying execution times.</p>
<p>We also demonstrated how to schedule system-wide recurring tasks by placing scripts in standard cron directories (<code>/etc/cron.hourly</code>, <code>/etc/cron.daily</code>, etc.) and how to create custom cron jobs in <code>/etc/cron.d</code>.</p>
<p>Finally, you explored advanced task scheduling with <code>systemd</code> timers, learning to create and enable service and timer units for recurring tasks, and how to manage temporary files and directories using <code>systemd-tmpfiles</code> for automated cleanup.</p>
<p>This comprehensive tutorial provided practical experience in managing diverse task scheduling needs on RHEL systems, from simple one-off commands to complex recurring system processes.</p>
<p>To practice the operations from this tutorial, try the interactive hands-on lab: <a target="_blank" href="https://labex.io/labs/rhel-schedule-tasks-in-red-hat-enterprise-linux-588897?course=red-hat-system-administration-rh134-labs">Schedule Tasks in Red Hat Enterprise Linux</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Configure Network Interfaces in Linux ]]>
                </title>
                <description>
                    <![CDATA[ Networking is an essential part of any Linux system. Proper networking allows communication between devices and the internet. Understanding the network interface is vital when setting up servers, solving connectivity issues, and managing device traff... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/configure-network-interfaces-in-linux/</link>
                <guid isPermaLink="false">6850922657a503eb47ff3b2b</guid>
                
                    <category>
                        <![CDATA[ networking ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Linux ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Eti Ijeoma ]]>
                </dc:creator>
                <pubDate>Mon, 16 Jun 2025 21:52:38 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1750110739161/ebf2347c-ac63-4fab-ad2f-5d9229e77eaa.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Networking is an essential part of any Linux system. Proper networking allows communication between devices and the internet. Understanding the network interface is vital when setting up servers, solving connectivity issues, and managing device traffic flow.</p>
<p>A common problem faced in networking is losing connectivity after modifying the network settings, which leads to an inability to access the system. This usually happens due to a misconfigured IP address, incorrect settings, and a poor understanding of network interface configurations.</p>
<p>In this article, we’ll guide you through understanding these network interface configurations, setting up and managing network interfaces on Linux, checking available interfaces, configuring static and dynamic IP addresses, and best practices to consider when setting up network interfaces. At the end of this article, you’ll have a solid foundation in network interfaces.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-are-network-interfaces">What are Network Interfaces?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-types-of-network-interfaces-in-linux">Types of Network Interfaces in Linux</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-why-network-interfaces-matter">Why Network Interfaces Matter</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-list-network-interfaces-in-linux">How to List Network Interfaces in Linux</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-configure-network-interfaces-in-linux">How to Configure Network Interfaces in Linux</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-set-up-a-network-bridge-in-linux">How to Set Up a Network Bridge in Linux</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-best-practices-for-configuring-network-interfaces-in-linux">Best Practices for Configuring Network Interfaces in Linux</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-what-are-network-interfaces">What are Network Interfaces?</h2>
<p>A network interface is a connection point within the Linux system that allows communication with other devices within the network<strong>.</strong> It is how the Linux kernel links the software side of the network with the hardware side. Linux systems provide many network interfaces that help to facilitate communication between the system and other external networks. </p>
<p>Linux network interfaces are essential for troubleshooting, configuration, management, and optimization of networking tasks. Understanding what they are and how they work allows you to optimize your server networking and security.</p>
<h2 id="heading-types-of-network-interfaces-in-linux">Types of Network Interfaces in Linux</h2>
<p>Network interfaces can be classified into two main categories: physical and virtual network interfaces.</p>
<h3 id="heading-physical-network-interfaces">Physical Network Interfaces</h3>
<p>Physical network adapters are the hardware components of the network interface that connect the system to a physical network. These physical networks include Wi-Fi and Ethernet. These adapters, commonly called Network Interface Cards (NIC), can be identified by their device names, such as wlan0 and eth0. They include the following:</p>
<ol>
<li><p><strong>Ethernet Interface (eth0, eth1, and so on)</strong></p>
<p> Ethernet interface is used for wired connections via an Ethernet card and helps configure high-speed networking. It can be used in data centres and servers. </p>
</li>
<li><p><strong>Wi-Fi interface (wlan0, wlan1, and so on)</strong></p>
<p> This represents a wireless network adapter, and it enables wireless connectivity via Wi-Fi networks to the servers.</p>
</li>
</ol>
<h3 id="heading-virtual-network-interfaces">Virtual Network Interfaces</h3>
<p>Virtual network interfaces are software-based interfaces managed by the Linux operating system. They integrate network virtualization technologies like Docker or KVM. There are several virtual network interfaces, and the most common ones include:</p>
<ul>
<li><p><strong>Loopback interface</strong>: This is a special interface that allows a system to communicate internally. It is permanently assigned the IP address 127.0.0.1, referred to as the <a target="_blank" href="http://localhost">localhost</a>.</p>
</li>
<li><p><strong>Bridge Interface</strong>: They are used to connect multiple network interfaces. It is useful for virtualization environments (for example, Linux KVM, Docker networking).</p>
</li>
<li><p><strong>Tunnel Interface</strong>: This is used for VPNs and networking tunnels. It helps to facilitate the passage of encrypted network traffic.</p>
</li>
</ul>
<h2 id="heading-why-network-interfaces-matter">Why Network Interfaces Matter</h2>
<p>Network interfaces form an essential component of a Linux system. It enables communication between devices and the internet, and properly configuring these interfaces provides the following benefits:</p>
<p><strong>Seamless connectivity</strong>: Network interfaces allow devices to communicate over local networks and the internet, enabling proper data exchange between servers and networks.</p>
<p><strong>Proper network management</strong>: Administrators can configure network interfaces by creating, managing, and assigning static or dynamic IPs and optimizing traffic flow.</p>
<p><strong>Improved security</strong>: Administrators can configure network interfaces with firewalls and VPNs to secure data and prevent unauthorized access.</p>
<p><strong>It provides support for virtualization and containerization</strong>: Virtual network interfaces provide proper communication between virtual machines, Docker containers, and other physical servers. This makes them essential for creating and managing DevOps environments.</p>
<h2 id="heading-how-to-list-network-interfaces-in-linux">How to List Network Interfaces in Linux</h2>
<p>You can check the available network interfaces within the Linux environment using the following commands.</p>
<ol>
<li><p><strong>Using the</strong> <code>ip</code> <strong>command:</strong></p>
<p> To list all network interfaces and their status, you can use the <code>ip link show</code> command. It displays details about the network interfaces, like the name, status, and MAC address.</p>
</li>
<li><p><strong>Using the</strong> <code>ifconfig</code> <strong>command</strong></p>
<p> To list all network interfaces, use this command: <code>ifconfig -a</code>. The command also displays details about the network interfaces and their current state.</p>
</li>
<li><p><strong>Using</strong> <a target="_blank" href="https://networkmanager.dev/docs/api/latest/nmcli.html"><code>nmcli</code></a> <strong>for NetworkManager-controlled systems</strong></p>
<p> To check the status of all network interfaces managed by NetworkManager, run:</p>
<p> <code>nmcli device status</code>.</p>
</li>
<li><p><strong>Using the</strong> <code>/sys/class/net/</code> <strong>directory</strong></p>
<p> To list all network interfaces, run <code>ls /sys/class/net/</code> This command is useful for scripting and automation because it provides a reliable way to check available interfaces programmatically.</p>
</li>
</ol>
<h2 id="heading-how-to-configure-network-interfaces-in-linux">How to Configure Network Interfaces in Linux</h2>
<p>Network interface configuration is essential for managing Linux servers and workstations. Understanding this configuration will help ensure smooth connectivity within your systems. This section will give you the correct information on configuring network interfaces.</p>
<h3 id="heading-assign-a-static-ip-address">Assign a Static IP Address</h3>
<p>A static IP address ensures the device maintains the same IP after each reboot. This is particularly useful for servers and devices that need consistent addressing. To assign a static IP address, the NetworkManager Command Line Interface (<strong>nmcli</strong>) provides a command-line utility to configure the network interface as shown below.</p>
<pre><code class="lang-bash">nmcli connection modify eth0 ipv4.addresses 192.168.1.100/24   <span class="hljs-comment"># set a static IPv4 address and subnet mask</span>

nmcli connection modify eth0 ipv4.gateway 192.168.1.1          <span class="hljs-comment"># define the default gateway</span>

nmcli connection modify eth0 ipv4.dns <span class="hljs-string">"8.8.8.8 8.8.4.4"</span>        <span class="hljs-comment"># configure primary and secondary DNS servers</span>

nmcli connection modify eth0 ipv4.method manual                <span class="hljs-comment"># switch the interface from DHCP to manual mode</span>

nmcli connection up eth0                                       <span class="hljs-comment"># bring the interface down and up to apply changes</span>
</code></pre>
<p>These commands set a fixed IP, gateway, and DNS on eth0, switch the interface to manual mode, and restart it so the new settings take effect. The settings persist across reboots because they are stored by <code>NetworkManager</code></p>
<h3 id="heading-assign-a-temporary-ip-address">Assign a Temporary IP Address</h3>
<p>The <code>ip</code> command lets you configure interfaces dynamically (not persistent across reboots):</p>
<pre><code class="lang-bash">ip addr add 192.168.1.100/24 dev eth0     <span class="hljs-comment"># assign 192.168.1.100/24 to interface eth0 (temporary)</span>

ip route add default via 192.168.1.1      <span class="hljs-comment"># set the default gateway to 192.168.1.1</span>
</code></pre>
<p>These two commands give eth0 the IP <code>192.168.1.100/24</code> and point all outbound traffic to the gateway <code>192.168.1.1</code>. The settings last only until the next reboot or interface reset.</p>
<h3 id="heading-assign-an-ip-address-with-ifconfig-deprecated">Assign an IP Address with ifconfig (deprecated)</h3>
<p>Older systems still ship with <code>ifconfig</code> and <code>route</code>. These commands are also temporary.</p>
<pre><code class="lang-bash">ifconfig eth0 192.168.1.100 netmask 255.255.255.0 up  <span class="hljs-comment"># assign 192.168.1.100/24 to eth0 and bring it up</span>

route add default gw 192.168.1.1 eth0                <span class="hljs-comment"># set the default gateway to 192.168.1.1 via eth0</span>
</code></pre>
<blockquote>
<p><strong>Note:</strong> Prefer <code>ip</code> or <code>nmcli</code> on modern systems.</p>
</blockquote>
<h3 id="heading-enable-dhcp-with-nmcli">Enable DHCP with nmcli</h3>
<p>A DHCP-assigned address lets the network hand out an IP address automatically.</p>
<pre><code class="lang-bash">nmcli connection modify eth0 ipv4.method auto   <span class="hljs-comment"># switch eth0 to use DHCP for automatic addressing</span>

nmcli connection up eth0                        <span class="hljs-comment"># restart the connection so the new DHCP setting takes effect</span>
</code></pre>
<p>To renew or request a lease directly:</p>
<pre><code class="lang-bash">dhclient eth0   <span class="hljs-comment"># manually request or renew an IP address via DHCP on interface eth0</span>
</code></pre>
<p>These commands set eth0 to use DHCP, restart the link so the change takes effect, and (optionally) trigger an instant lease renewal.</p>
<h3 id="heading-assign-multiple-ip-addresses-to-one-interface">Assign Multiple IP Addresses to One Interface</h3>
<p>A network interface can have multiple addresses assigned to it, making it applicable to host multiple services on a single interface.</p>
<p><strong>Using IP command (Temporary Assignment)</strong></p>
<pre><code class="lang-bash">ip addr add 192.168.1.101/24 dev eth0   <span class="hljs-comment"># add an extra IPv4 address to eth0 (temporary)</span>

ip addr add 2001:db8::1/64 dev eth0     <span class="hljs-comment"># add an IPv6 address to eth0 (temporary)</span>
</code></pre>
<p>These two commands attach an extra IPv4 and an IPv6 address to eth0 until the interface resets or the system reboots</p>
<p><strong>Persistent Configuration (Netplan)</strong></p>
<p>Edit the <code>/etc/netplan/01-netcfg.yaml</code> file:</p>
<pre><code class="lang-bash">network:

  version: 2

  renderer: networkd

  ethernets:

    eth0:

      addresses:

        - 192.168.1.100/24

        - 192.168.1.101/24

        - 2001:db8::1/64
</code></pre>
<p>After editing the file, run <code>sudo netplan apply</code> to make the additional addresses stick across reboots.</p>
<h2 id="heading-how-to-set-up-a-network-bridge-in-linux">How to Set Up a Network Bridge in Linux</h2>
<p>A network bridge allows multiple interfaces to act as a single network segment, which is useful in virtualization (KVM, Docker).</p>
<p><strong>Using</strong> <code>brctl</code> <strong>(bridge-utils package)</strong></p>
<pre><code class="lang-bash">brctl addbr br0                       <span class="hljs-comment"># create a new bridge interface named br0</span>

brctl addif br0 eth0                  <span class="hljs-comment"># add physical interface eth0 to the bridge</span>

ip addr add 192.168.1.100/24 dev br0  <span class="hljs-comment"># assign an IP address to the bridge, not to eth0</span>

ip link <span class="hljs-built_in">set</span> br0 up                    <span class="hljs-comment"># bring the bridge interface online</span>
</code></pre>
<p>These commands create bridge br0, attach eth0 to it, give the bridge its own IP, and bring it online.</p>
<h4 id="heading-ia"> </h4>
<p><strong>Using nmcli (for NetworkManager-managed systems)</strong></p>
<pre><code class="lang-bash">nmcli connection add <span class="hljs-built_in">type</span> bridge ifname br0                       <span class="hljs-comment"># create a new bridge named br0</span>

nmcli connection modify br0 bridge.stp no                         <span class="hljs-comment"># turn off Spanning Tree Protocol</span>

nmcli connection add <span class="hljs-built_in">type</span> bridge-slave ifname eth0 master br0     <span class="hljs-comment"># attach physical interface eth0 to br0</span>

nmcli connection up br0                                           <span class="hljs-comment"># bring the bridge online so settings take effect</span>
</code></pre>
<p>This sequence builds the same bridge through NetworkManager, disables <a target="_blank" href="https://en.wikipedia.org/wiki/Spanning_Tree_Protocol">STP</a> for faster convergence, links eth0 as a slave, and activates the bridge so guests can reach the network.</p>
<h2 id="heading-best-practices-for-configuring-network-interfaces-in-linux">Best Practices for Configuring Network Interfaces in Linux</h2>
<h3 id="heading-make-your-configurations-persistent"><strong>Make Your Configurations Persistent</strong></h3>
<p>One of the mistakes network engineers make in Linux networking is making changes that do not persist after rebooting. While specific commands can modify the network settings temporarily, they do not save these changes permanently.</p>
<p>To ensure that these network settings survive server reboots, modify system configuration files such as <code>/etc/network/interfaces</code>. Once you ensure that all changes are persistent, there will be no unexpected disruptions when a system restarts.</p>
<h3 id="heading-assign-static-ips-for-servers"><strong>Assign Static IPs for Servers</strong></h3>
<p>Static IP addresses are the best for servers and critical infrastructure. Unlike DHCP addresses, which can change over time, static IP addresses are more stable and reliable. For services like web hosting and database management, static IPs play a key role, as IP addresses do not need to change.</p>
<h3 id="heading-secure-your-network-interfaces"><strong>Secure Your Network Interfaces</strong></h3>
<p>Network interfaces are the entry points into a system, so if they are misconfigured, they could pose a considerable security risk. To reduce attacks, administrators should turn off all unused network interfaces by modifying the configuration file to prevent automatic activation. Additionally, you should use firewall tools to control the traffic that tries to reach the system.</p>
<h3 id="heading-monitor-your-network-interfaces"><strong>Monitor Your Network Interfaces</strong></h3>
<p>As a system administrator, monitoring network interfaces helps prevent downtime and ensure proper network reliability. You can check the status of your network interfaces by running commands like <code>link show</code> or <code>if-config -a</code>. You can also monitor them in real time using tools like Netstat. Monitoring your systems ensures that network issues are detected early enough, reducing downtime and improving network stability.</p>
<h3 id="heading-constantly-update-network-packages"><strong>Constantly Update Network Packages</strong></h3>
<p>You must constantly update network management tools and drivers because it helps to implement security patches and other performance improvements, as outdated network packages can cause security vulnerabilities. There are specific network-related packages such as <code>network-manager</code>, <code>bridge-utils</code> and <code>iproute2</code>.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Setting up network interfaces in Linux is a fundamental skill every system administrator should have. Whether configuring static IP addresses or enabling DHCP, understanding these concepts will ensure that your systems are stable and have proper connectivity. Implementing best practices like monitoring traffic and securing the network interface gives you the best results. As you continue working with Linux, you can experiment with different configurations to deepen your understanding of network interfaces.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Get Information About Your Linux System Through the Command Line ]]>
                </title>
                <description>
                    <![CDATA[ Whether you’ve just gained access to a new Linux system, ethically hacked into one as part of a security test, or you’re just curious to know more about your current machine, this article will guide you through the process. You’ll learn how you can g... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/get-linux-system-info-through-cli/</link>
                <guid isPermaLink="false">68495738cb7b75f7a33a73c4</guid>
                
                    <category>
                        <![CDATA[ Linux ]]>
                    </category>
                
                    <category>
                        <![CDATA[ handbook ]]>
                    </category>
                
                    <category>
                        <![CDATA[ cli ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Zaira Hira ]]>
                </dc:creator>
                <pubDate>Wed, 11 Jun 2025 10:15:20 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1749636399891/4b457f71-2d18-463a-b98a-e19ff5a6b769.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Whether you’ve just gained access to a new Linux system, ethically hacked into one as part of a security test, or you’re just curious to know more about your current machine, this article will guide you through the process.</p>
<p>You’ll learn how you can get information related to your OS (operating system), kernel, CPU, memory, processes, disks, networks, and installed software. You’ll explore the commands and their outputs in detail.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-why-its-important-to-understand-your-linux-system">Why It's Important to Understand Your Linux System</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-get-your-os-amp-kernel-information-in-linux">How to Get Your OS &amp; Kernel Information in Linux</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-get-your-cpu-information-in-linux">How to Get Your CPU Information in Linux</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-get-your-memory-information-in-linux">How to Get Your Memory Information in Linux</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-get-your-disk-amp-filesystem-information-in-linux">How to Get Your Disk &amp; Filesystem Information in Linux</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-get-your-hardware-information-in-linux">How to Get Your Hardware Information in Linux</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-get-your-network-interfaces-amp-status-information-in-linux">How to Get Your Network Interfaces &amp; Status Information in Linux</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-get-your-software-amp-services-information-in-linux">How to Get Your Software &amp; Services Information in Linux</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-get-your-logs-amp-dmesg-in-formation-in-linux">How to Get Your Logs &amp; Dmesg In formation in Linux</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-get-your-securityuser-audit-information-in-linux">How to Get Your Security/User Audit Information in Linux</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-visually-appealing-commands">Visually Appealing Commands</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-why-its-important-to-understand-your-linux-system">Why It's Important to Understand Your Linux System</h2>
<h3 id="heading-system-administration">System Administration</h3>
<p>System administrators need to have an understanding of the system so they are able to:</p>
<ul>
<li><p>Manage users, groups, and permissions effectively.</p>
</li>
<li><p>Configure services like web servers, databases, and so on.</p>
</li>
<li><p>Automate repetitive tasks with scripts and cron jobs.</p>
</li>
</ul>
<h3 id="heading-troubleshooting">Troubleshooting</h3>
<p>When the system is in a problematic state, a solid understanding of the system specification and configuration helps you to:</p>
<ul>
<li><p>Identify and resolve system errors quickly.</p>
</li>
<li><p>Analyze system logs and monitor performance.</p>
</li>
<li><p>Diagnose network and hardware issues.</p>
</li>
</ul>
<h3 id="heading-security-auditing">Security Auditing</h3>
<p>If you are in a security related role, knowing your system in depth helps you to:</p>
<ul>
<li><p>Monitor logs for unauthorized access.</p>
</li>
<li><p>Configure firewalls and security policies.</p>
</li>
<li><p>Detect and remove malicious processes or software.</p>
</li>
</ul>
<h3 id="heading-performance-optimization">Performance Optimization</h3>
<p>If you know how to gather information related to system resources, you can measure them and create a projection for the future use. You can also:</p>
<ul>
<li><p>Tune system parameters for better efficiency.</p>
</li>
<li><p>Monitor resource usage (CPU, memory, disk, I/O).</p>
</li>
<li><p>Eliminate bottlenecks and optimize workloads.</p>
</li>
</ul>
<h3 id="heading-proactive-maintenance">Proactive Maintenance</h3>
<p>It is a good practice to be able to prevent issues before they occur. Once you know your system well, you can:</p>
<ul>
<li><p>Schedule regular updates and backups.</p>
</li>
<li><p>Ensure system reliability and uptime.</p>
</li>
</ul>
<p>Understanding your Linux system gives you greater control, enhances system stability, and improves your overall effectiveness as a system administrator or power user.</p>
<p>In the next section, we’ll discuss some essential commands for gathering system information.</p>
<h2 id="heading-how-to-get-your-os-amp-kernel-information-in-linux">How to Get Your OS &amp; Kernel Information in Linux</h2>
<h3 id="heading-uname-a-command"><code>uname -a</code> Command</h3>
<p><code>uname -a</code> provides full kernel information:</p>
<pre><code class="lang-bash">uname -a
Linux ip-172-31-90-178 6.8.0-1024-aws <span class="hljs-comment">#26-Ubuntu SMP Tue Feb 18 17:22:37 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux</span>
</code></pre>
<p>Here is what each part means in the above command:</p>
<ul>
<li><p><code>Linux</code>: The kernel name.</p>
</li>
<li><p><code>ip-172-31-90-178</code>: The network hostname of the system.</p>
</li>
<li><p><code>6.8.0-1024-aws</code>: The kernel version and AWS-specific build.</p>
</li>
<li><p><code>#26-Ubuntu</code>: The kernel build number.</p>
</li>
<li><p><code>SMP</code>: Symmetric Multi-Processing, indicating that the kernel is compiled for multiple processors.</p>
</li>
<li><p><code>Tue Feb 18 17:22:37 UTC 2025</code>: The date and time when the kernel was compiled.</p>
</li>
<li><p><code>x86_64 x86_64 x86_64</code>: The machine hardware name (architecture), processor type, and platform type, all indicating 64-bit x86 architecture.</p>
</li>
<li><p><code>GNU/Linux</code>: The operating system name.</p>
</li>
</ul>
<p>Based on this output, I’m running on an AWS EC2 instance with a 64-bit Ubuntu Linux distribution using a kernel that was specifically built for AWS infrastructure.</p>
<h3 id="heading-uname-r-and-uname-s-commands"><code>uname -r</code> and <code>uname -s</code> Commands</h3>
<p>The <code>uname -r</code> and <code>uname -s</code> commands specify the kernel version and OS type information:</p>
<pre><code class="lang-bash">uname -r
6.11.0-25-generic

uname -s
Linux
</code></pre>
<h3 id="heading-cat-etcos-release-command"><code>cat /etc/os-release</code> Command</h3>
<p>The <code>cat /etc/os-release</code> command provides distribution information:</p>
<pre><code class="lang-bash">cat /etc/os-release
PRETTY_NAME=<span class="hljs-string">"Ubuntu 24.04.2 LTS"</span>
NAME=<span class="hljs-string">"Ubuntu"</span>
VERSION_ID=<span class="hljs-string">"24.04"</span>
VERSION=<span class="hljs-string">"24.04.2 LTS (Noble Numbat)"</span>
VERSION_CODENAME=noble
ID=ubuntu
ID_LIKE=debian
HOME_URL=<span class="hljs-string">"https://www.ubuntu.com/"</span>
SUPPORT_URL=<span class="hljs-string">"https://help.ubuntu.com/"</span>
BUG_REPORT_URL=<span class="hljs-string">"https://bugs.launchpad.net/ubuntu/"</span>
PRIVACY_POLICY_URL=<span class="hljs-string">"https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"</span>
UBUNTU_CODENAME=noble
LOGO=ubuntu-logo
</code></pre>
<p>Here is what each part means in the above command:</p>
<ul>
<li><p><code>PRETTY_NAME="Ubuntu 24.04.2 LTS"</code>: The user-friendly name of the distribution including version and LTS (Long Term Support) designation.</p>
</li>
<li><p><code>NAME="Ubuntu"</code>: The name of the Linux distribution.</p>
</li>
<li><p><code>VERSION_ID="24.04"</code>: The version number of the Ubuntu release (Year/Month format).</p>
</li>
<li><p><code>VERSION="24.04.2 LTS (Noble Numbat)"</code>: The complete version information including:</p>
<p>  • <code>24.04</code>: Major version (released April 2024)</p>
<p>  • <code>.2</code>: Point release number</p>
<p>  • <code>LTS</code>: Long Term Support</p>
<p>  • <code>Noble Numbat</code>: The release codename</p>
</li>
<li><p><code>VERSION_CODENAME=noble</code>: The codename for this Ubuntu release ("Noble").</p>
</li>
<li><p><code>ID=ubuntu</code>: The machine-readable name of the operating system.</p>
</li>
<li><p><code>ID_LIKE=debian</code>: Indicates that Ubuntu is based on Debian Linux.</p>
</li>
<li><p><code>HOME_URL</code>, <code>SUPPORT_URL</code>, <code>BUG_REPORT_URL</code>, <code>PRIVACY_POLICY_URL</code> : Various official URLs for Ubuntu resources.</p>
</li>
<li><p><code>UBUNTU_CODENAME=noble</code>: Reiterates the codename of this Ubuntu release.</p>
</li>
<li><p><code>LOGO=ubuntu-logo</code>: Specifies the logo identifier for the distribution.</p>
</li>
</ul>
<p>This output shows that I’m running Ubuntu 24.04.2 LTS (codenamed "Noble Numbat"), which is a Long Term Support release of Ubuntu. Being an LTS version means it will receive security updates and support for an extended period (typically 5 years for Ubuntu LTS releases).</p>
<h3 id="heading-hostnamectl-command"><code>hostnamectl</code> Command</h3>
<p><code>hostnamectl</code> shows the hostname, OS, and kernel info:</p>
<pre><code class="lang-bash">hostnamectl
 Static hostname: ip-172-31-90-178
       Icon name: computer-vm
         Chassis: vm 🖴
      Machine ID: ec272830b6dca2da0d11e41b292cfc99
         Boot ID: dd12f48ff01b44a796991d99ce1bcfde
  Virtualization: xen
Operating System: Ubuntu 24.04.2 LTS              
          Kernel: Linux 6.8.0-1024-aws
    Architecture: x86-64
 Hardware Vendor: Xen
  Hardware Model: HVM domU
Firmware Version: 4.11.amazon
   Firmware Date: Thu 2006-08-24
    Firmware Age: 18y 9month 1w 2d
</code></pre>
<p>In the above command, here is what each part means:</p>
<ul>
<li><p><code>Static hostname: "ip-172-31-90-178"</code>: This is the permanent hostname of the system, stored in <code>/etc/hostname</code>.</p>
</li>
<li><p><code>Icon name: "computer-vm"</code>: A symbolic icon identifier for the system, used by some desktop environments.</p>
</li>
<li><p><code>Chassis: "vm"</code>: Indicates this is running in a virtual machine environment.</p>
</li>
<li><p><code>Machine ID: "ec272830b6dca2da0d11e41b292cfc99"</code>: A unique identifier for this system, stored in <code>/etc/machine-id</code>.</p>
</li>
<li><p><code>Boot ID: "dd12f48ff01b44a796991d99ce1bcfde"</code>: A unique identifier that changes with each system boot.</p>
</li>
<li><p><code>Virtualization: "xen"</code>: Shows that this system is running on Xen virtualization (common for AWS instances).</p>
</li>
<li><p><code>Operating System: "Ubuntu 24.04.2 LTS"</code>: The current OS distribution and version.</p>
</li>
<li><p><code>Kernel: "Linux 6.8.0-1024-aws"</code>: The current Linux kernel version, specifically an AWS-optimized kernel.</p>
</li>
<li><p><code>Architecture: "x86-64"</code>: The CPU architecture of the system.</p>
</li>
<li><p><code>Hardware Vendor: "Xen" Hardware Model: "HVM domU"</code>: Indicates this is a Xen HVM (Hardware Virtual Machine) domain user instance.</p>
</li>
<li><p>Firmware Details:</p>
<ul>
<li><p><code>Version: 4.11.amazon</code>: This is the version of the firmware/BIOS specifically customized for AWS environments.</p>
</li>
<li><p><code>Date: Thu 2006-08-24</code>: This is the release date of the firmware. The date might seem old (2006) but this is normal for AWS instances.</p>
</li>
<li><p><code>Age: 18y 9month 1w</code> : This shows how old the firmware is relative to the current date calculated from the firmware date (2006) to now (2025). While the firmware seems old, it is still maintained and secure.</p>
</li>
</ul>
</li>
</ul>
<p>This overall output shows that I’m running Ubuntu 24.04.2 LTS on an AWS EC2 instance using Xen virtualization. The system is using an AWS-optimized kernel and is configured as a HVM (Hardware Virtual Machine) instance.</p>
<h2 id="heading-how-to-get-your-cpu-information-in-linux">How to Get Your CPU Information in Linux</h2>
<h3 id="heading-lscpu-command"><code>lscpu</code> Command</h3>
<p><code>lscpu</code> shows CPU architecture, cores, threads, and virtualization information:</p>
<pre><code class="lang-bash">lscpu
Architecture:             x86_64
  CPU op-mode(s):         32-bit, 64-bit
  Address sizes:          46 bits physical, 48 bits virtual
  Byte Order:             Little Endian
CPU(s):                   1
  On-line CPU(s) list:    0
Vendor ID:                GenuineIntel
  Model name:             Intel(R) Xeon(R) CPU E5-2686 v4 @ 2
                          .30GHz
    CPU family:           6
    Model:                79
    Thread(s) per core:   1
    Core(s) per socket:   1
    Socket(s):            1
    Stepping:             1
    BogoMIPS:             4599.99
    Flags:                fpu vme de pse tsc msr pae mce cx8 
                          apic sep mtrr pge mca cmov pat pse3
                          6 clflush mmx fxsr sse sse2 ht sysc
                          all nx rdtscp lm constant_tsc rep_g
                          ood nopl xtopology cpuid tsc_known_
                          freq pni pclmulqdq ssse3 fma cx16 p
                          cid sse4_1 sse4_2 x2apic movbe popc
                          nt tsc_deadline_timer aes xsave avx
                           f16c rdrand hypervisor lahf_lm abm
                           pti fsgsbase bmi1 avx2 smep bmi2 e
                          rms invpcid xsaveopt
Virtualization features:  
  Hypervisor vendor:      Xen
  Virtualization <span class="hljs-built_in">type</span>:    full
Caches (sum of all):      
  L1d:                    32 KiB (1 instance)
  L1i:                    32 KiB (1 instance)
  L2:                     256 KiB (1 instance)
  L3:                     45 MiB (1 instance)
NUMA:                     
  NUMA node(s):           1
  NUMA node0 CPU(s):      0
Vulnerabilities:          
  Gather data sampling:   Not affected
  Itlb multihit:          KVM: Mitigation: VMX unsupported
  L1tf:                   Mitigation; PTE Inversion
  Mds:                    Vulnerable: Clear CPU buffers attem
                          pted, no microcode; SMT Host state 
                          unknown
  Meltdown:               Mitigation; PTI
  Mmio stale data:        Vulnerable: Clear CPU buffers attem
                          pted, no microcode; SMT Host state 
                          unknown
  Reg file data sampling: Not affected
  Retbleed:               Not affected
  Spec rstack overflow:   Not affected
  Spec store bypass:      Vulnerable
  Spectre v1:             Mitigation; usercopy/swapgs barrier
                          s and __user pointer sanitization
  Spectre v2:             Mitigation; Retpolines; STIBP disab
                          led; RSB filling; PBRSB-eIBRS Not a
                          ffected; BHI Retpoline
  Srbds:                  Not affected
  Tsx async abort:        Not affected
</code></pre>
<p>Here is a brief explanation of the output above:</p>
<p>1. Basic CPU Info</p>
<ul>
<li><p>Architecture: <code>x86_64</code> (64-bit)</p>
</li>
<li><p>CPU Model: Intel Xeon E5-2686 v4 (2.3 GHz)</p>
</li>
<li><p>Cores/Threads: 1 core, 1 thread (no Hyper-Threading)</p>
</li>
<li><p>Physical CPU (Socket): 1</p>
</li>
</ul>
<p>2. Performance &amp; Features</p>
<ul>
<li><p>Cache Sizes:</p>
<ul>
<li><p>L1: 32 KiB (data) + 32 KiB (instructions)</p>
</li>
<li><p>L2: 256 KiB</p>
</li>
<li><p>L3: 45 MiB (large, typical for Xeon)</p>
</li>
</ul>
</li>
<li><p>Flags: Supports AVX, AES, SSE4.1/4.2 (useful for encryption/vector ops).</p>
</li>
</ul>
<p>3. Virtualization</p>
<ul>
<li><p>Hypervisor: Running on Xen (full virtualization).</p>
</li>
<li><p>Virtualization Support: Yes (Intel VT-x).</p>
</li>
</ul>
<p>4. Security (Vulnerabilities)</p>
<ul>
<li><p>Meltdown/Spectre: Mostly mitigated (PTI, Retpolines).</p>
</li>
<li><p>MDS/MMIO: Vulnerable (no microcode fixes).</p>
</li>
<li><p>Spec Store Bypass: Vulnerable (no mitigation).</p>
</li>
</ul>
<p>5. NUMA (Memory)</p>
<ul>
<li>Single NUMA node (no multi-processor complexity).</li>
</ul>
<p>The output shows that my machine is a single-core Intel Xeon (in a virtualized/cloud environment) with large L3 cache but has some unpatched CPU vulnerabilities.</p>
<h3 id="heading-cat-proccpuinfo-command"><code>cat /proc/cpuinfo</code> Command</h3>
<p><code>cat /proc/cpuinfo</code> provides more in-depth details about the CPU:</p>
<pre><code class="lang-bash">cat /proc/cpuinfo 
processor    : 0
vendor_id    : GenuineIntel
cpu family    : 6
model        : 79
model name    : Intel(R) Xeon(R) CPU E5-2686 v4 @ 2.30GHz
stepping    : 1
microcode    : 0xd000404
cpu MHz        : 2299.998
cache size    : 46080 KB
physical id    : 0
siblings    : 1
core id        : 0
cpu cores    : 1
apicid        : 0
initial apicid    : 0
fpu        : yes
fpu_exception    : yes
cpuid level    : 13
wp        : yes
flags        : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx rdtscp lm constant_tsc rep_good nopl xtopology cpuid tsc_known_freq pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm pti fsgsbase bmi1 avx2 smep bmi2 erms invpcid xsaveopt
bugs        : cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass l1tf mds swapgs itlb_multihit mmio_stale_data bhi
bogomips    : 4599.99
clflush size    : 64
cache_alignment    : 64
address sizes    : 46 bits physical, 48 bits virtual
power management:
</code></pre>
<h3 id="heading-nproc-command"><code>nproc</code> Command</h3>
<p><code>nproc</code> shows the core count:</p>
<pre><code class="lang-bash">nproc
1
</code></pre>
<p>The above command output shows there is one available processor.</p>
<h2 id="heading-how-to-get-your-memory-information-in-linux">How to Get Your Memory Information in Linux</h2>
<h3 id="heading-free-h-command"><code>free -h</code> Command</h3>
<p>You can use the <code>free -h</code> command to know the total/used/free RAM:</p>
<pre><code class="lang-bash">free -h
               total        used        free      shared  buff/cache   available
Mem:           957Mi       406Mi       218Mi       920Ki       522Mi       551Mi
Swap:             0B          0B          0B
</code></pre>
<p>Here is a breakdown of the output shared above:</p>
<ul>
<li><p><code>total</code>: The total amount of physical memory (RAM) or swap space available on the system.</p>
</li>
<li><p><code>used</code>: The amount of memory currently being used by applications and the system. Calculated as: <code>total - free - buffers - cache</code>.</p>
</li>
<li><p><code>free</code>: The amount of memory that is completely unused.</p>
</li>
<li><p><code>shared</code>: Memory that may be simultaneously accessed by multiple programs.</p>
</li>
<li><p><code>buff/cache</code>: Combines two types of memory:</p>
<ul>
<li><p>Buffers: Memory used for block device I/O buffering.</p>
</li>
<li><p>Cache: Memory used for file system page cache - This memory can be reclaimed when needed by applications.</p>
</li>
<li><p><code>available</code>: It includes the 'free' memory plus memory that can be reclaimed from <code>buff/cache</code>. This is the most important column for determining if you have enough memory.</p>
</li>
</ul>
</li>
</ul>
<h3 id="heading-vmstat-command"><strong>vmstat</strong> Command</h3>
<p><code>vmstat</code> stands for Virtual Memory Statistics, a tool to monitor system performance. It provides information about memory usage, CPU activity, Processes, Disk I/O and Swap usage.</p>
<p>You can also use <code>vmstat</code> to extract live information. Here is how you can do that:</p>
<pre><code class="lang-bash">vmstat 1 5
procs -----------memory---------- ---swap-- -----io---- -system-- -------cpu-------
 r  b   swpd   free   buff  cache   si   so    bi    bo   <span class="hljs-keyword">in</span>   cs us sy id wa st gu
 1  0      0 238264  46120 489056    0    0     3     8   23    0  0  0 82  0 18  0
 0  0      0 238264  46120 489060    0    0     0     0  240  120  0  1 98  0  1  0
 0  0      0 238264  46120 489060    0    0     0     0  239  124  0  0 98  0  2  0
 0  0      0 238264  46120 489060    0    0     0     0  199  101  0  0 95  0  5  0
 0  0      0 238264  46120 489060    0    0     0     0   36   25  0  0 78  0 22  0
</code></pre>
<p>Here is what the above command is doing:</p>
<ol>
<li><p>Captures 5 snapshots of system performance.</p>
</li>
<li><p>Each snapshot is taken 1 second apart, giving near real-time insights.</p>
</li>
<li><p>Displays key metrics about:</p>
<ul>
<li><p>Memory usage (free, buffered, cached).</p>
</li>
<li><p>CPU activity (user, system, idle, waiting).</p>
</li>
<li><p>Processes (running, blocked).</p>
</li>
<li><p>Disk I/O (blocks read/written).</p>
</li>
<li><p>Swap usage (if swapping is happening).</p>
</li>
</ul>
</li>
</ol>
<p>Note that, you can replace the interval and number of snapshots accordingly.</p>
<p>Here’s a detailed breakdown of the output above:</p>
<ul>
<li><p><code>Procs</code>:</p>
<ul>
<li><p><code>r</code>: Number of processes waiting for run time.</p>
</li>
<li><p><code>b</code>: Number of processes in uninterruptible sleep</p>
</li>
</ul>
</li>
<li><p><code>Memory</code> (in KB):</p>
<ul>
<li><p><code>swpd</code>: Amount of virtual memory used</p>
</li>
<li><p><code>free</code>: Amount of idle memory</p>
</li>
<li><p><code>buff</code>: Memory used as buffers</p>
</li>
<li><p><code>cache</code>: Memory used as cache</p>
</li>
</ul>
</li>
<li><p><code>Swap</code>:</p>
<ul>
<li><p><code>si</code>: Memory swapped in from disk (KB/s)</p>
</li>
<li><p><code>so</code>: Memory swapped out to disk (KB/s)</p>
</li>
</ul>
</li>
<li><p><code>IO</code>:</p>
<ul>
<li><p><code>bi</code>: Blocks received from a block device (blocks/s)</p>
</li>
<li><p><code>bo</code>: Blocks sent to a block device (blocks/s)</p>
</li>
</ul>
</li>
<li><p><code>System</code>:</p>
<ul>
<li><p><code>in</code>: Number of interrupts per second</p>
</li>
<li><p><code>cs</code>: Number of context switches per second</p>
</li>
</ul>
</li>
<li><p><code>CPU</code> (percentages):</p>
<ol>
<li><p><code>us</code>: Time spent running user code</p>
</li>
<li><p><code>sy</code>: Time spent running system code</p>
</li>
<li><p><code>id</code>: Time spent idle</p>
</li>
<li><p><code>wa</code>: Time spent waiting for IO</p>
</li>
<li><p><code>st</code>: Time stolen from a virtual machine</p>
</li>
<li><p><code>gu</code>: Time running guest code (virtual CPU)</p>
</li>
</ol>
</li>
</ul>
<p>From the output, you can see that my system:</p>
<ul>
<li><p>Has very low CPU usage (high idle percentage)</p>
</li>
<li><p>Has no swap being used (<code>swpd = 0</code>)</p>
</li>
<li><p>Has about <code>99MB</code> free memory</p>
</li>
<li><p>Shows minimal IO activity</p>
</li>
<li><p>Is running in a virtualized environment (notice the <code>st</code> (stolen) time column has non-zero value</p>
</li>
</ul>
<p>The first line shows averages since the last reboot, while subsequent lines show the real-time statistics for each second.</p>
<h3 id="heading-cat-procmeminfo-command"><code>cat /proc/meminfo</code> Command</h3>
<p><code>cat /proc/meminfo</code> shows detailed memory stats:</p>
<pre><code class="lang-bash">cat /proc/meminfo
MemTotal:         980384 kB
MemFree:          245100 kB
MemAvailable:     585896 kB
Buffers:           46184 kB
Cached:           393672 kB
SwapCached:            0 kB
Active:           141404 kB
Inactive:         356376 kB
Active(anon):      47672 kB
Inactive(anon):    29300 kB
Active(file):      93732 kB
Inactive(file):   327076 kB
Unevictable:       36528 kB
Mlocked:           27152 kB
SwapTotal:             0 kB
SwapFree:              0 kB
Zswap:                 0 kB
Zswapped:              0 kB
Dirty:                 0 kB
Writeback:             0 kB
AnonPages:         94488 kB
Mapped:            97936 kB
Shmem:               920 kB
KReclaimable:      95396 kB
Slab:             148672 kB
SReclaimable:      95396 kB
SUnreclaim:        53276 kB
KernelStack:        2444 kB
PageTables:         3224 kB
SecPageTables:         0 kB
NFS_Unstable:          0 kB
Bounce:                0 kB
WritebackTmp:          0 kB
CommitLimit:      490192 kB
Committed_AS:     508912 kB
VmallocTotal:   34359738367 kB
VmallocUsed:        9988 kB
VmallocChunk:          0 kB
Percpu:            14848 kB
HardwareCorrupted:     0 kB
AnonHugePages:         0 kB
ShmemHugePages:        0 kB
ShmemPmdMapped:        0 kB
FileHugePages:         0 kB
FilePmdMapped:         0 kB
Unaccepted:            0 kB
HugePages_Total:       0
HugePages_Free:        0
HugePages_Rsvd:        0
HugePages_Surp:        0
Hugepagesize:       2048 kB
Hugetlb:               0 kB
DirectMap4k:       71680 kB
DirectMap2M:      976896 kB
</code></pre>
<p>Here is a detailed breakdown of the output shared above:</p>
<ul>
<li><p>Total Memory and Available Memory:</p>
<ul>
<li><p><code>MemTotal</code>: Total physical RAM available.</p>
</li>
<li><p><code>MemFree</code>: Completely unused memory.</p>
</li>
<li><p><code>MemAvailable</code>: Memory available for new applications.</p>
</li>
</ul>
</li>
<li><p>Memory Caches and Buffers:</p>
<ul>
<li><p><code>Buffers</code>: Memory used for block device I/O buffering.</p>
</li>
<li><p><code>Cached</code>: Memory used for file system cache.</p>
</li>
<li><p><code>SwapCached</code>: Memory pages stored in both RAM and swap.</p>
</li>
</ul>
</li>
<li><p>Active vs Inactive Memory:</p>
<ul>
<li><p><code>Active</code>: Recently used memory.</p>
</li>
<li><p><code>Inactive</code>: Less recently used memory.</p>
</li>
<li><p><code>Active(anon)</code>: Recently used anonymous memory.</p>
</li>
<li><p><code>Active(file)</code>: Recently used file-backed memory.</p>
</li>
</ul>
</li>
<li><p>Swap Information:</p>
<ul>
<li><p><code>SwapTotal</code>: Swap space configured.</p>
</li>
<li><p><code>SwapFree</code>: Swap space available.</p>
</li>
<li><p><code>Zswap</code>: Compressed swap in RAM.</p>
</li>
</ul>
</li>
<li><p>Other Important Metrics:</p>
<ul>
<li><p><code>Dirty</code>: Memory waiting to be written to disk.</p>
</li>
<li><p><code>Mapped</code>: Files mapped into memory.</p>
</li>
<li><p><code>Slab</code>: Kernel data structures cache.</p>
</li>
<li><p><code>CommitLimit</code>: Total memory available for allocation.</p>
</li>
<li><p><code>Committed_AS</code>: Total memory currently allocated.</p>
</li>
</ul>
</li>
</ul>
<p>A healthy memory usage is indicated by a good amount of available memory, active caching mechanisms in place and no memory pressure (no swap usage needed).</p>
<h2 id="heading-how-to-get-your-disk-amp-filesystem-information-in-linux">How to Get Your Disk &amp; Filesystem Information in Linux</h2>
<h3 id="heading-tree-d-l-1-command"><code>tree -d -L 1</code> Command</h3>
<p><code>tree -d -L 1</code> shows the file system details from the folder it is executed in. To find the complete file system details, run it from the root <code>/</code> folder:</p>
<pre><code class="lang-bash">tree -d -L 1
.
├── bin -&gt; usr/bin
├── bin.usr-is-merged
├── boot
├── dev
├── etc
├── home
├── lib -&gt; usr/lib
├── lib.usr-is-merged
├── lib64 -&gt; usr/lib64
├── lost+found
├── media
├── mnt
├── opt
├── proc
├── root
├── run
├── sbin -&gt; usr/sbin
├── sbin.usr-is-merged
├── snap
├── srv
├── sys
├── tmp
├── usr
└── var

25 directories
</code></pre>
<p>The command output of <code>tree -d -L 1</code> shows a directory tree structure with the following options:</p>
<ul>
<li><p><code>-d</code>: Shows only directories (ignores files)</p>
</li>
<li><p><code>-L 1</code>: Limits the depth of the tree to one level (only shows the immediate subdirectories)</p>
</li>
<li><p><code>df -h</code>: mounted filesystems and usage:</p>
<pre><code class="lang-bash">  df -h
  Filesystem      Size  Used Avail Use% Mounted on
  /dev/root        29G  2.6G   26G   9% /
  tmpfs           479M     0  479M   0% /dev/shm
  tmpfs           192M  908K  191M   1% /run
  tmpfs           5.0M     0  5.0M   0% /run/lock
  /dev/xvda16     881M  144M  676M  18% /boot
  /dev/xvda15     105M  6.1M   99M   6% /boot/efi
  tmpfs            96M   12K   96M   1% /run/user/1000
</code></pre>
<p>  The above output from the <code>df -h</code> command shows the following disk space usage information:</p>
<ul>
<li><p><code>Filesystem</code>: The name of the mounted filesystem/device.</p>
</li>
<li><p><code>Size</code>: Total size of the filesystem.</p>
</li>
<li><p><code>Used</code>: Amount of space used.</p>
</li>
<li><p><code>Avail</code>: Amount of space available.</p>
</li>
<li><p><code>Use%</code>: Percentage of space used.</p>
</li>
<li><p><code>Mounted on</code>: The mount point where the filesystem is attached</p>
</li>
</ul>
</li>
</ul>
<h3 id="heading-lsblk-command"><code>lsblk</code> Command</h3>
<p><code>lsblk</code> stands for ‘list block devices’ and shows information about all available block devices like hard drives, SSDs, and so on.</p>
<pre><code class="lang-bash">lsblk
NAME     MAJ:MIN RM  SIZE RO TYPE MOUNTPOINTS
loop0      7:0    0 26.3M  1 loop /snap/amazon-ssm-agent/9881
loop1      7:1    0 73.9M  1 loop /snap/core22/1748
loop2      7:2    0 44.4M  1 loop /snap/snapd/23545
loop3      7:3    0 50.9M  1 loop /snap/snapd/24505
loop4      7:4    0 73.9M  1 loop /snap/core22/1963
loop5      7:5    0 27.2M  1 loop /snap/amazon-ssm-agent/11320
xvda     202:0    0   30G  0 disk 
├─xvda1  202:1    0   29G  0 part /
├─xvda14 202:14   0    4M  0 part 
├─xvda15 202:15   0  106M  0 part /boot/efi
└─xvda16 259:0    0  913M  0 part /boot
</code></pre>
<p>The output above shows the following details:</p>
<ul>
<li><p><code>NAME</code>: Device name.</p>
</li>
<li><p><code>MAJ:MIN</code>: Major and minor device numbers.</p>
</li>
<li><p><code>RM</code>: Removable flag (1 for removable, 0 for fixed).</p>
</li>
<li><p><code>SIZE</code>: Device size.</p>
</li>
<li><p><code>RO</code>: Read-only flag (1 for read-only, 0 for read-write).</p>
</li>
<li><p><code>TYPE</code>: Device type (disk, part for partition, loop for loop device).</p>
</li>
<li><p><code>MOUNTPOINTS</code>: Where the device is mounted.</p>
</li>
</ul>
<h3 id="heading-fdisk-l-command"><code>fdisk -l</code> Command</h3>
<p><code>fdisk -l</code> shows all disk devices and their partitions on your system:</p>
<pre><code class="lang-bash">Disk /dev/xvda: 30 GiB, 32212254720 bytes, 62914560 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel <span class="hljs-built_in">type</span>: gpt
Disk identifier: E3478E01-32E3-4FC2-8E79-1BCCDE89C2D7

Device        Start      End  Sectors  Size Type
/dev/xvda1  2099200 62914526 60815327   29G Linux filesystem
/dev/xvda14    2048    10239     8192    4M BIOS boot
/dev/xvda15   10240   227327   217088  106M EFI System
/dev/xvda16  227328  2097152  1869825  913M Linux extended boot
</code></pre>
<p>The above output shows the partition information for the the main system disk (<code>/dev/xvda</code>) which is 30 GiB in size and has four partitions:</p>
<ul>
<li><p><code>/dev/xvda1</code>: <code>29G</code> Linux filesystem (main system partition).</p>
</li>
<li><p><code>/dev/xvda14</code>: <code>4M</code> BIOS boot partition.</p>
</li>
<li><p><code>/dev/xvda15</code>: <code>106M</code> EFI System partition (for UEFI boot).</p>
</li>
<li><p><code>/dev/xvda16</code>: <code>913M</code> Linux extended boot partition.</p>
</li>
</ul>
<h3 id="heading-mount-command"><code>mount</code> Command</h3>
<p><code>mount</code> shows all currently mounted filesystems in the format: <code>device/source "on" mount_point "type" filesystem_type (mount_options)</code>, displaying where and how each filesystem is attached to your system's directory tree.</p>
<p>Here is an example line from the output of <code>mount</code>:</p>
<pre><code class="lang-bash">/dev/xvda1 on / <span class="hljs-built_in">type</span> ext4 (rw,relatime,discard,errors=remount-ro,commit=30)
</code></pre>
<p>Some common mount options you’ll see are:</p>
<ul>
<li><p><code>rw</code>: Read-write access.</p>
</li>
<li><p><code>ro</code>: Read-only access.</p>
</li>
<li><p><code>nosuid</code>: Disable SUID/SGID bits.</p>
</li>
<li><p><code>nodev</code>: Prevent device file interpretation.</p>
</li>
<li><p><code>noexec</code>: Prevent execution of binaries.</p>
</li>
<li><p><code>relatime</code>: Update access times relatively.</p>
</li>
</ul>
<h3 id="heading-du-sh-command"><code>du -sh *</code> Command</h3>
<p><code>du -sh *</code> provides a summary of the disk usage for each file and directory in the current directory (good for finding disk hogs):</p>
<pre><code class="lang-bash">du -sh *
4.0K    file1.txt
8.0K    file2.txt
12K     directory1
20K     directory2
</code></pre>
<h2 id="heading-how-to-get-your-hardware-information-in-linux">How to Get Your Hardware Information in Linux</h2>
<h3 id="heading-lshw-command"><code>lshw</code> Command</h3>
<p>The <code>lshw</code> command provides detailed information about the computer's hardware configuration. It can report:</p>
<ul>
<li><p>Memory configuration.</p>
</li>
<li><p>Firmware version.</p>
</li>
<li><p>Mainboard configuration.</p>
</li>
<li><p>CPU version and speed.</p>
</li>
<li><p>Cache configuration.</p>
</li>
<li><p>Bus speed and more.</p>
</li>
</ul>
<p>It's particularly useful for system administrators and users who need to gather detailed hardware information. The command can output information in various formats including HTML, XML, JSON, or plain text.</p>
<p>Here is a portion of the output from <code>lshw</code>:</p>
<pre><code class="lang-bash">*-pci
          description: Host bridge
          product: 440FX - 82441FX PMC [Natoma]
          vendor: Intel Corporation
          physical id: 100
          bus info: pci@0000:00:00.0
          version: 02
          width: 32 bits
          clock: 33MHz
        *-isa
             description: ISA bridge
             product: 82371SB PIIX3 ISA [Natoma/Triton II]
             vendor: Intel Corporation
             physical id: 1
             bus info: pci@0000:00:01.0
             version: 00
             width: 32 bits
             clock: 33MHz
             capabilities: isa bus_master
             configuration: latency=0
</code></pre>
<h3 id="heading-lspci-command"><code>lspci</code> Command</h3>
<p><code>lspci</code> displays information about all PCI (Peripheral Component Interconnect) buses and devices connected to your system.</p>
<pre><code class="lang-bash">lspci
00:00.0 Host bridge: Intel Corporation 440FX - 82441FX PMC [Natoma] (rev 02)
00:01.0 ISA bridge: Intel Corporation 82371SB PIIX3 ISA [Natoma/Triton II]
00:01.1 IDE interface: Intel Corporation 82371SB PIIX3 IDE [Natoma/Triton II]
00:01.3 Bridge: Intel Corporation 82371AB/EB/MB PIIX4 ACPI (rev 01)
00:02.0 VGA compatible controller: Cirrus Logic GD 5446
00:03.0 Unassigned class [ff80]: XenSource, Inc. Xen Platform Device (rev 01)
</code></pre>
<p>From the output, we can see that:</p>
<ul>
<li><p>Each line starts with a <code>bus:device.function</code> address (like "<code>00:00.0</code>")</p>
</li>
<li><p>Following the address is the device class and the specific hardware details:</p>
<ul>
<li><p>A Host bridge (<code>Intel 440FX)</code>, which manages communications between the CPU and other components.</p>
</li>
<li><p>An ISA bridge (<code>Intel PIIX3</code>), for legacy device support.</p>
</li>
<li><p>An IDE interface for storage devices.</p>
</li>
<li><p>An ACPI bridge for power management.</p>
</li>
<li><p>A VGA graphics controller (Cirrus Logic).</p>
</li>
<li><p>A Xen Platform Device (this suggests you're running in a Xen virtualized environment).</p>
</li>
</ul>
</li>
</ul>
<p>The command is particularly useful for:</p>
<ul>
<li><p>Troubleshooting hardware issues</p>
</li>
<li><p>Verifying hardware detection</p>
</li>
<li><p>Finding hardware details for driver installation</p>
</li>
<li><p>Checking system configuration</p>
</li>
</ul>
<h2 id="heading-how-to-get-your-network-interfaces-amp-status-information-in-linux">How to Get Your Network Interfaces &amp; Status Information in Linux</h2>
<h3 id="heading-ip-a-command"><code>ip a</code> Command</h3>
<p><code>ip a</code> displays information about all network interfaces on your system:</p>
<pre><code class="lang-bash">ip -a
1: lo: &lt;LOOPBACK,UP,LOWER_UP&gt;
- This is the loopback interface (localhost)
- MTU (Maximum Transmission Unit) is 65536 bytes
- IP address: 127.0.0.1/8 (IPv4)
- IPv6 address: ::1/128

2. Network Interface (enX0):
enX0: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt;
- This is your main network interface
- MTU is 9001 bytes
- MAC address (link/ether): 12:16:a6:d3:b3:61
- IPv4 address: 172.31.90.178/20
- IPv6 address: fe80::1016:a6ff:fed3:b361/64 (Link-local)
</code></pre>
<p>Here are the key elements in the output:</p>
<ul>
<li><p>Interface state (UP/DOWN).</p>
</li>
<li><p>MAC address (link/ether).</p>
</li>
<li><p>IPv4 and IPv6 addresses.</p>
</li>
<li><p>Network scope (host, global, link).</p>
</li>
<li><p>Address validity lifetime (valid_lft).</p>
</li>
<li><p>Broadcast address (brd).</p>
</li>
</ul>
<h3 id="heading-ip-r-command"><code>ip r</code> Command</h3>
<p><code>ip r</code> shows the system’s routing table:</p>
<pre><code class="lang-bash">ip r
default via 172.31.80.1 dev enX0 proto dhcp src 172.31.90.178 metric 100 
172.31.0.2 via 172.31.80.1 dev enX0 proto dhcp src 172.31.90.178 metric 100 
172.31.80.0/20 dev enX0 proto kernel scope link src 172.31.90.178 metric 100 
172.31.80.1 dev enX0 proto dhcp scope link src 172.31.90.178 metric 100
</code></pre>
<p>The above <code>ip r</code> output shows my system's routing table with the following routes:</p>
<ul>
<li><p>Default Route (Gateway):</p>
<ul>
<li><p>Default via <code>172.31.80.1</code>: All traffic not matching other rules goes through this gateway.</p>
</li>
<li><p>Using interface <code>enX0</code>.</p>
</li>
<li><p>Configured via DHCP.</p>
</li>
<li><p>Source IP: <code>172.31.90.178</code>.</p>
</li>
</ul>
</li>
<li><p>Local Network:</p>
<ul>
<li><p><code>172.31.80.0/20</code>: Local subnet (covers IPs from <code>172.31.80.0</code> to <code>172.31.95.255</code>)</p>
</li>
<li><p>Directly connected to <code>enX0</code> interface</p>
</li>
<li><p>Kernel-managed route (proto kernel)</p>
</li>
<li><p>For packets originating from <code>172.31.90.178</code></p>
</li>
</ul>
</li>
<li><p>DHCP Route:</p>
<ul>
<li><p>Direct route to DHCP server (<code>172.31.80.1</code>)</p>
</li>
<li><p>Via interface <code>enX0</code></p>
</li>
</ul>
</li>
</ul>
<p>All routes have a metric of 100, which determines route priority (lower values are preferred).</p>
<p><code>netstat -tuln</code> shows active listening ports:</p>
<pre><code class="lang-bash">netstat -tuln
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State      
tcp        0      0 127.0.0.54:53           0.0.0.0:*               LISTEN     
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN     
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN     
tcp6       0      0 :::80                   :::*                    LISTEN     
tcp6       0      0 :::22                   :::*                    LISTEN     
udp        0      0 127.0.0.54:53           0.0.0.0:*                          
udp        0      0 127.0.0.53:53           0.0.0.0:*                          
udp        0      0 172.31.90.178:68        0.0.0.0:*                          
udp        0      0 127.0.0.1:323           0.0.0.0:*                          
udp6       0      0 ::1:323                 :::*
</code></pre>
<h2 id="heading-how-to-get-your-software-amp-services-information-in-linux">How to Get Your Software &amp; Services Information in Linux</h2>
<h3 id="heading-installed-packages">Installed packages</h3>
<p>You can check installed packages with <code>dpkg -l</code>, <code>apt list --installed</code> (Debian/Ubuntu). Here is a snippet from the output:</p>
<pre><code class="lang-bash">vim-common/noble-updates,noble-security,now 2:9.1.0016-1ubuntu7.8 all [installed,automatic]
vim-runtime/noble-updates,noble-security,now 2:9.1.0016-1ubuntu7.8 all [installed,automatic]
vim-tiny/noble-updates,noble-security,now 2:9.1.0016-1ubuntu7.8 amd64 [installed,automatic]
vim/noble-updates,noble-security,now 2:9.1.0016-1ubuntu7.8 amd64 [installed,automatic]
</code></pre>
<h3 id="heading-service-status">Service status</h3>
<p><code>systemctl list-units --type=service</code> lists the services. You can also use <code>systemctl status &lt;service&gt;</code> and replace <code>&lt;service&gt;</code> with the one you want.</p>
<p>Here’s the output for <code>cron.service</code>:</p>
<pre><code class="lang-bash">systemctl status cron.service
● cron.service - Regular background program processing daemon
     Loaded: loaded (/usr/lib/systemd/system/cron.service; enabled; preset: enabled)
     Active: active (running) since Wed 2025-05-14 19:46:58 UTC; 2 weeks 5 days ago
       Docs: man:cron(8)
   Main PID: 625 (cron)
      Tasks: 1 (<span class="hljs-built_in">limit</span>: 1129)
     Memory: 1.7M (peak: 4.7M)
        CPU: 20.890s
     CGroup: /system.slice/cron.service
             └─625 /usr/sbin/cron -f -P

Jun 03 09:25:01 ip-172-31-90-178 CRON[121748]: pam_unix(cron:session): session closed <span class="hljs-keyword">for</span> user root
Jun 03 09:35:01 ip-172-31-90-178 CRON[121817]: pam_unix(cron:session): session opened <span class="hljs-keyword">for</span> user root(uid=0) by root(uid=0)
Jun 03 09:35:01 ip-172-31-90-178 CRON[121818]: (root) CMD (<span class="hljs-built_in">command</span> -v debian-sa1 &gt; /dev/null &amp;&amp; debian-sa1 1 1)
Jun 03 09:35:01 ip-172-31-90-178 CRON[121817]: pam_unix(cron:session): session closed <span class="hljs-keyword">for</span> user root
Jun 03 09:45:01 ip-172-31-90-178 CRON[122050]: pam_unix(cron:session): session opened <span class="hljs-keyword">for</span> user root(uid=0) by root(uid=0)
Jun 03 09:45:01 ip-172-31-90-178 CRON[122051]: (root) CMD (<span class="hljs-built_in">command</span> -v debian-sa1 &gt; /dev/null &amp;&amp; debian-sa1 1 1)
Jun 03 09:45:01 ip-172-31-90-178 CRON[122050]: pam_unix(cron:session): session closed <span class="hljs-keyword">for</span> user root
Jun 03 09:55:01 ip-172-31-90-178 CRON[122318]: pam_unix(cron:session): session opened <span class="hljs-keyword">for</span> user root(uid=0) by root(uid=0)
Jun 03 09:55:01 ip-172-31-90-178 CRON[122319]: (root) CMD (<span class="hljs-built_in">command</span> -v debian-sa1 &gt; /dev/null &amp;&amp; debian-sa1 1 1)
Jun 03 09:55:01 ip-172-31-90-178 CRON[122318]: pam_unix(cron:session): session closed <span class="hljs-keyword">for</span> user root
lines 5-21/21 (END)
</code></pre>
<h3 id="heading-processes"><strong>Processes</strong></h3>
<p><code>ps aux</code> shows all processes with their respective status:</p>
<pre><code class="lang-bash">ps aux
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root           1  0.0  1.4  22556 13952 ?        Ss   May14   0:35 /usr/lib/systemd/systemd --system --deserialize=63
root           2  0.0  0.0      0     0 ?        S    May14   0:00 [kthreadd]
root           3  0.0  0.0      0     0 ?        S    May14   0:00 [pool_workqueue_release]
root           4  0.0  0.0      0     0 ?        I&lt;   May14   0:00 [kworker/R-rcu_g]
root           5  0.0  0.0      0     0 ?        I&lt;   May14   0:00 [kworker/R-rcu_p]
root           6  0.0  0.0      0     0 ?        I&lt;   May14   0:00 [kworker/R-slub_]
.
.
.
</code></pre>
<p>Here's an explanation of each column in the <code>ps aux</code> output:</p>
<ul>
<li><p><code>USER</code>: The owner of the process</p>
</li>
<li><p><code>PID</code>: Process ID number</p>
</li>
<li><p><code>%CPU</code>: CPU usage percentage</p>
</li>
<li><p><code>%MEM</code>: Memory usage percentage</p>
</li>
<li><p><code>VSZ</code>: Virtual Memory Size in kilobytes (total program size)</p>
</li>
<li><p><code>RSS</code>: Resident Set Size in kilobytes (actual memory used)</p>
</li>
<li><p><code>TTY</code>: Terminal associated with the process ('?' means no terminal)</p>
</li>
<li><p><code>STAT</code>: Process state code:</p>
<ul>
<li><p><code>S</code>: Sleeping</p>
</li>
<li><p><code>R</code>: Running</p>
</li>
<li><p><code>I</code>: Idle</p>
</li>
<li><p><code>Z</code>: Zombie</p>
</li>
<li><p><code>T</code>: Stopped</p>
</li>
<li><p><code>s</code>: Session leader</p>
</li>
<li><p><code>&lt;</code>: High priority</p>
</li>
<li><p><code>N</code>: Low priority</p>
</li>
</ul>
</li>
<li><p><code>START</code>: Time when the process started</p>
</li>
<li><p><code>TIME</code>: Cumulative CPU time used</p>
</li>
<li><p><code>COMMAND</code>: The command with all its arguments</p>
</li>
</ul>
<h3 id="heading-top-and-htop-commands"><code>top</code> and <code>htop</code> Commands</h3>
<p><code>top</code> or <code>htop</code> can be used for live usage overview, and for showing a dynamic view of system performance and running processes. Here's what it displays:</p>
<ul>
<li><p>System Overview:</p>
<ul>
<li><p>System uptime and number of logged-in users.</p>
</li>
<li><p>Load average values for the last 1, 5, and 15 minutes.</p>
</li>
<li><p>Total number of processes and their states (running, sleeping, stopped, zombie)</p>
</li>
</ul>
</li>
<li><p>Resource Usage:</p>
<ul>
<li><p>CPU usage breakdown (user, system, idle, etc.).</p>
</li>
<li><p>Memory usage (total, free, used, cached).</p>
</li>
<li><p>Swap space usage</p>
</li>
<li><p>Process List:Shows a sorted list of running processes (by default sorted by CPU usage)For each process, displays:</p>
<ul>
<li><p>Process ID (PID).</p>
</li>
<li><p>User who owns the process.</p>
</li>
<li><p>CPU and memory usage.</p>
</li>
<li><p>Process priority and nice value.</p>
</li>
<li><p>Memory usage details (virtual, resident, shared).</p>
</li>
<li><p>Process status.</p>
</li>
<li><p>Running time.</p>
</li>
<li><p>Command name.</p>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<pre><code class="lang-bash">    top - 10:04:25 up 19 days, 14:17,  1 user,  load average: 0.00, 0.00, 0.00
    Tasks: 104 total,   1 running, 103 sleeping,   0 stopped,   0 zombie
    %Cpu(s):  0.0 us,  0.0 sy,  0.0 ni, 88.0 id,  0.0 wa,  0.0 hi,  0.0 si, 12.0 st 
    MiB Mem :    957.4 total,    247.3 free,    366.1 used,    533.7 buff/cache     
    MiB Swap:      0.0 total,      0.0 free,      0.0 used.    591.3 avail Mem 

        PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND                                              
          1 root      20   0   22556  13952   9728 S   0.0   1.4   0:35.08 systemd                                              
          2 root      20   0       0      0      0 S   0.0   0.0   0:00.16 kthreadd                                             
          3 root      20   0       0      0      0 S   0.0   0.0   0:00.00 pool_workqueue_release                               
          4 root       0 -20       0      0      0 I   0.0   0.0   0:00.00 kworker/R-rcu_g                                      
          5 root       0 -20       0      0      0 I   0.0   0.0   0:00.00 kworker/R-rcu_p                                      
          6 root       0 -20       0      0      0 I   0.0   0.0   0:00.00 kworker/R-slub_                                      
          7 root       0 -20       0      0      0 I   0.0   0.0   0:00.00 kworker/R-netns                                      
         10 root       0 -20       0      0      0 I   0.0   0.0   0:00.00 kworker/0:0H-events_highpri                          
         12 root       0 -20       0      0      0 I   0.0   0.0   0:00.00 kworker/R-mm_pe                                      
         13 root      20   0       0      0      0 I   0.0   0.0   0:00.00 rcu_tasks_rude_kthread                               
         14 root      20   0       0      0      0 I   0.0   0.0   0:00.00 rcu_tasks_trace_kthread
</code></pre>
<p>    The top command updates this information regularly (by default every 3 seconds) and is commonly used for:</p>
<ul>
<li><p>Monitoring system performance</p>
</li>
<li><p>Identifying resource-intensive processes</p>
</li>
<li><p>Troubleshooting system slowdowns</p>
</li>
<li><p>Getting a quick overview of system health</p>
<p>  You can also interact with top while it's running using various keyboard commands (like 'k' to kill a process, '1' to see cpu cores, etc.).</p>
</li>
</ul>
<h2 id="heading-how-to-get-your-logs-amp-dmesg-in-formation-in-linux">How to Get Your Logs &amp; Dmesg In formation in Linux</h2>
<p>Based on the system configuration, a number of logs are generated. These can be audit logs, system logs, cron logs, and so on. They all carry useful information. Here are some commands that you can use to view logs:</p>
<ul>
<li><p><code>dmesg | less</code>: Kernel ring buffer (hardware issues, boot messages)</p>
</li>
<li><p><code>journalctl -xe</code>: Recent critical logs (systemd systems)</p>
</li>
<li><p><code>/var/log/syslog</code> or <code>/var/log/messages</code>: General system logs</p>
</li>
</ul>
<h2 id="heading-how-to-get-your-securityuser-audit-information-in-linux">How to Get Your Security/User Audit Information in Linux</h2>
<p><code>whoami</code> shows the current user’s username.</p>
<pre><code class="lang-bash">whoami
ubuntu
</code></pre>
<p><code>id</code> shows detailed information about a user's identity on the system.</p>
<pre><code class="lang-bash">id
uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu),4(adm),24(cdrom),27(sudo),30(dip),105(lxd)
</code></pre>
<p>Let's break down the output:</p>
<ul>
<li><p>User ID (uid): <code>uid=1000(ubuntu)</code> means the user ID is 1000, with username "ubuntu"</p>
</li>
<li><p>Primary Group ID (gid): <code>gid=1000(ubuntu)</code> means the primary group ID is 1000, named "ubuntu"</p>
</li>
<li><p>Supplementary Groups (groups): The user belong to the following groups:</p>
<ul>
<li><p><code>ubuntu (1000)</code>: Your primary group.</p>
</li>
<li><p><code>adm (4)</code>: For system monitoring tasks.</p>
</li>
<li><p><code>cdrom (24)</code>: For accessing CD-ROM devices.</p>
</li>
<li><p><code>sudo (27)</code>: Allows you to execute commands with superuser privileges.</p>
</li>
<li><p><code>dip (30)</code>: For managing dial-up connections.</p>
</li>
<li><p><code>lxd (105)</code>: For managing LXD containers.</p>
</li>
</ul>
</li>
</ul>
<p>The <code>id</code> command is useful for checking user and group IDs, verifying group memberships, troubleshooting permissions issues and confirming sudo access.</p>
<p><code>who</code> displays information about users currently logged into the system:</p>
<pre><code class="lang-bash">who
ubuntu   pts/0        2025-06-03 08:45 (39.43.159.5)
</code></pre>
<p>The output breakdown is shown below:</p>
<ul>
<li><p>Username: "<code>ubuntu</code>"</p>
</li>
<li><p>Terminal: "<code>pts/0</code>" (pseudo-terminal)</p>
</li>
<li><p>Login time: "<code>2025-06-03 08:45"</code></p>
</li>
<li><p>Remote host: "<code>(39.43.159.5)</code>" - the IP address from where the connection was made</p>
</li>
<li><p><code>w</code>- shows who is logged in and what they are doing:</p>
</li>
</ul>
<pre><code class="lang-bash">w
 10:21:46 up 19 days, 14:35,  1 user,  load average: 0.00, 0.00, 0.00
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU  WHAT
ubuntu   pts/0    39.43.159.5      08:45   44:56   0.00s  0.02s sshd: ubuntu [priv]
</code></pre>
<p>Here is the result breakdown:</p>
<p>First line:</p>
<ul>
<li><p><code>10:21:46</code>: Current system time</p>
</li>
<li><p><code>up 19 days, 14:35</code>: System uptime (how long the system has been running)</p>
</li>
<li><p><code>1 user</code>: Number of users currently logged in</p>
</li>
<li><p><code>load average: 0.24, 0.05, 0.02</code>: System load averages for the past 1, 5, and 15 minutes</p>
<ul>
<li><p>Numbers below 1.0 indicate low system load</p>
</li>
<li><p>Higher numbers indicate more system load/stress</p>
</li>
</ul>
</li>
</ul>
<p>Second line shows the column headers for the user information below:</p>
<ul>
<li><p><code>USER</code>: Username.</p>
</li>
<li><p><code>TTY</code>: Terminal device being used.</p>
</li>
<li><p><code>FROM</code>: Remote host from where the user is connected.</p>
</li>
<li><p><code>LOGIN@</code>: Time when the user logged in.</p>
</li>
<li><p><code>IDLE</code>: Time since the user's last activity.</p>
</li>
<li><p><code>JCPU</code>: CPU time used by all processes attached to the tty.</p>
</li>
<li><p><code>PCPU</code>: CPU time used by the current process.</p>
</li>
<li><p><code>WHAT</code>: Current process/command being run.</p>
</li>
</ul>
<p><code>last</code> shows a history of user logins and system reboots:</p>
<pre><code class="lang-bash">last
ubuntu   pts/1        39.43.159.5      Tue Jun  3 10:15 - 10:17  (00:02)
ubuntu   pts/0        39.43.159.5      Tue Jun  3 08:45   still logged <span class="hljs-keyword">in</span>
ubuntu   pts/0        39.43.159.5      Tue Jun  3 05:23 - 08:29  (03:06)
ubuntu   pts/0        39.43.159.5      Sun Jun  1 06:32 - 12:24  (05:52)
ubuntu   pts/0        39.43.159.5      Thu May 22 05:39 - 05:58  (00:18)
ubuntu   pts/0        139.135.32.93    Wed May 21 14:45 - 14:47  (00:01)
ubuntu   pts/0        139.135.32.93    Wed May 21 11:58 - 13:49  (01:51)
ubuntu   pts/0        39.43.159.5      Wed May 21 05:05 - 05:12  (00:06)
ubuntu   pts/0        39.43.159.5      Tue May 20 18:41 - 21:45  (03:04)
ubuntu   pts/0        39.43.159.5      Thu May 15 06:12 - 06:12  (00:00)
ubuntu   pts/0        39.43.159.5      Thu May 15 06:05 - 06:12  (00:07)
ubuntu   pts/0        18.206.107.27    Wed May 14 20:06 - 20:08  (00:01)
ubuntu   pts/0        182.185.185.39   Wed May 14 19:48 - 19:50  (00:01)
reboot   system boot  6.8.0-1024-aws   Wed May 14 19:46   still running

wtmp begins Wed May 14 19:46:47 2025
</code></pre>
<p>Each line shows:</p>
<ul>
<li><p>Username (in this case, all logins are from 'ubuntu' user).</p>
</li>
<li><p>Terminal device (<code>pts/0</code> indicates a pseudo-terminal, typically used for SSH connections).</p>
</li>
<li><p>Remote host IP address (where the connection came from).</p>
</li>
<li><p>Login time and date.</p>
</li>
<li><p>Logout time or status.</p>
</li>
<li><p>Session duration in parentheses.</p>
</li>
</ul>
<p><code>sudo -l</code> shows what the current user can do with sudo.</p>
<pre><code class="lang-bash">sudo -l
Matching Defaults entries <span class="hljs-keyword">for</span> ubuntu on ip-172-31-90-178:
    env_reset, mail_badpass, secure_path=/usr/<span class="hljs-built_in">local</span>/sbin\:/usr/<span class="hljs-built_in">local</span>/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin,
    use_pty

User ubuntu may run the following commands on ip-172-31-90-178:
    (ALL : ALL) ALL
    (ALL) NOPASSWD: ALL
</code></pre>
<p>This output indicates that the 'ubuntu' user has:</p>
<ul>
<li><p>Full sudo access (can execute any command)</p>
</li>
<li><p>No password requirement for sudo commands</p>
</li>
<li><p>Complete administrative privileges on the system</p>
</li>
</ul>
<h2 id="heading-visually-appealing-commands">Visually Appealing Commands</h2>
<p>In this section you’ll learn about two commands that display the information we have seen before in a presentable and aesthetic form.</p>
<p><code>neofetch</code> - displays system info along with the distribution logo:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748945743174/9cef1af7-fce8-4657-ad26-7d75b5755dd1.png" alt="Terminal output of the neofetch command displaying Ubuntu system information, including OS, kernel, uptime, CPU, GPU, memory, and a colorful ASCII logo" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p><code>btop</code> displays dynamic stats with different modes:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748945510465/8c8c200c-bb1a-4123-8db7-c30bb6a1c9bf.gif" alt="A realtime snapshot of the btop system monitor showing real-time CPU, memory, disk, and network usage in a terminal. Colorful graphs display performance metrics for processes, temperatures, and uptime" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Thank you for reading the article until the end. If you found it helpful, consider sharing it with others.</p>
<p><strong>Stay Connected and Continue Your Learning Journey!</strong></p>
<p>I read every message, come say hi 👋</p>
<ol>
<li><p><strong>Connect with me on</strong>:</p>
<ul>
<li><p><a target="_blank" href="https://www.linkedin.com/in/zaira-hira/">LinkedIn</a>: I share content related to Linux, Cyber security and DevOps. Leave a recommendation on LinkedIn and endorse me on relevant skills.</p>
</li>
<li><p><a target="_blank" href="https://discord.gg/9zfbjEDs">Discord</a> community: Hang around with other devs or share your accomplishments.</p>
</li>
<li><p><a target="_blank" href="https://twitter.com/hira_zaira">X</a>: I share pre-launch updates and some behind the scenes.</p>
</li>
</ul>
</li>
<li><p><strong>Get access to exclusive content</strong>: For one-on-one help and exclusive content go <a target="_blank" href="https://buymeacoffee.com/zairah/extras">here</a>.</p>
</li>
</ol>
<p>My <a target="_blank" href="https://www.freecodecamp.org/news/author/zaira/">articles</a> are part of my mission to increase accessibility to quality content for everyone. Each piece takes a lot of time and effort to write. This article will be free, forever. If you've enjoyed my work and want to keep me motivated, consider <a target="_blank" href="https://buymeacoffee.com/zairah">buying me a coffee</a>.</p>
<p>Thank you once again and happy learning!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Use Wireshark Filters to Analyze Your Network Traffic ]]>
                </title>
                <description>
                    <![CDATA[ Wireshark is an open-source tool widely regarded as the gold standard for network packet analysis. It allows you to capture live network traffic or inspect pre-recorded capture files, breaking down the data into individual packets for detailed examin... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/use-wireshark-filters-to-analyze-network-traffic/</link>
                <guid isPermaLink="false">67ee83d004f007db33e0f920</guid>
                
                    <category>
                        <![CDATA[ #cybersecurity ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Wireshark ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Linux ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Hang Hu ]]>
                </dc:creator>
                <pubDate>Thu, 03 Apr 2025 12:49:20 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1743684532493/cc26aa99-fc7a-4b47-ab16-60dac77561fd.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Wireshark is an open-source tool widely regarded as the gold standard for network packet analysis. It allows you to capture live network traffic or inspect pre-recorded capture files, breaking down the data into individual packets for detailed examination.</p>
<p>You can use Wireshark in scenarios like troubleshooting network performance issues (for example, slow connections or dropped packets), investigating suspicious activity (like detecting malware or unauthorized access), or learning how protocols like HTTP, TCP, or DNS function in real-world environments.</p>
<p>For beginners, think of it as a window into the invisible world of network communication, revealing what’s happening behind the scenes when you browse the web, send an email, or stream a video. Its power lies in its ability to provide granular insights, making it an indispensable tool for network administrators, cybersecurity enthusiasts, and anyone curious about how networks operate.</p>
<p>In this tutorial, you will learn how to use Wireshark display filters to analyze network traffic and spot potential security threats. Wireshark is a powerful network protocol analyzer that can capture and dissect network packets, which is crucial for cybersecurity professionals.</p>
<h3 id="heading-heres-what-well-cover">Here’s what we’ll cover:</h3>
<ul>
<li><p><a class="post-section-overview" href="#heading-how-to-start-wireshark-and-analyze-network-traffic">How to Start Wireshark and Analyze Network Traffic</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-work-with-network-capture-files">How to Work with Network Capture Files</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-understanding-the-wireshark-interface">Understanding the Wireshark Interface</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-understanding-and-applying-basic-display-filters">Understanding and Applying Basic Display Filters</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-advanced-filtering-techniques">Advanced Filtering Techniques</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-analyzing-security-related-traffic">Analyzing Security-Related Traffic</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-analyzing-sample-traffic-and-generating-new-traffic">Analyzing Sample Traffic and Generating New Traffic</a></p>
</li>
</ul>
<h2 id="heading-prerequisites"><strong>Prerequisites</strong></h2>
<p>Before we start, you'll need to know <strong>Linux Basic Syntax.</strong> You can learn it through this <a target="_blank" href="https://labex.io/skilltrees/linux">Linux Skill Tree</a>.</p>
<p>Don't worry if you're new to <a target="_blank" href="https://labex.io/skilltrees/wireshark"><strong>Wireshark</strong></a> – I’ll explain everything as we go.</p>
<h2 id="heading-how-to-start-wireshark-and-analyze-network-traffic"><strong>How to Start Wireshark and Analyze Network Traffic</strong></h2>
<p>In this step, we're going to start using Wireshark. First, you'll learn how to launch it. Then, you'll either capture network traffic or use a provided sample file for analysis. Understanding the Wireshark interface is crucial, as it helps you view and analyze packet data.</p>
<h3 id="heading-installing-wireshark-on-ubuntu-2204">Installing Wireshark on Ubuntu 22.04</h3>
<p>Before you can start using Wireshark, you need to install it. Open a terminal window and run the following commands:</p>
<pre><code class="lang-bash">sudo apt update
sudo apt install wireshark -y
</code></pre>
<h3 id="heading-launching-wireshark"><strong>Launching Wireshark</strong></h3>
<p>To start Wireshark, you need to open a terminal window. You can do this by clicking on the terminal icon in the taskbar or by pressing <code>Ctrl+Alt+T</code>. Once the terminal is open, you'll use a command to start Wireshark. In the terminal, type the following command and press Enter:</p>
<pre><code class="lang-bash">wireshark
</code></pre>
<p>This command tells your system to start the Wireshark application. After a few seconds, Wireshark will open. You should see a window similar to the one shown below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743385586635/78f76c20-c8d0-48d2-bdb7-17ff3f5fc261.png" alt="Wireshark Main Interface Example" class="image--center mx-auto" width="1666" height="678" loading="lazy"></p>
<h2 id="heading-how-to-work-with-network-capture-files"><strong>How to Work with Network Capture Files</strong></h2>
<p>For this part of the tutorial, you have two options:</p>
<h3 id="heading-option-1-use-the-provided-sample-file"><strong>Option 1: Use the Provided Sample File</strong></h3>
<pre><code class="lang-bash"><span class="hljs-comment"># Download a sample packet capture file with mixed traffic</span>
wget -q https://s3.amazonaws.com/tcpreplay-pcap-files/smallFlows.pcap -O /home/labex/project/sample.pcapng

<span class="hljs-comment"># Make sure the user has access to the file</span>
chmod 644 /home/labex/project/sample.pcapng
</code></pre>
<p>I’ve prepared a sample capture file for you at <code>/home/labex/project/sample.pcapng</code>. This file contains a variety of network traffic that you can analyze.</p>
<p>To open this file:</p>
<ol>
<li><p>In Wireshark, go to File &gt; Open</p>
</li>
<li><p>Navigate to <code>/home/labex/project/sample.pcapng</code></p>
</li>
<li><p>Click "Open"</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743385612148/dbeb3d39-db15-4363-a499-e8b527b43d84.png" alt="Wireshark Open File Screenshot" class="image--center mx-auto" width="2606" height="2036" loading="lazy"></p>
<p>The file will load in Wireshark, showing various packets that have been captured previously.</p>
<h3 id="heading-option-2-capture-your-own-traffic"><strong>Option 2: Capture Your Own Traffic</strong></h3>
<p>If you prefer to capture your own traffic:</p>
<ol>
<li><p>In the Wireshark main window, look for the list of available network interfaces.</p>
</li>
<li><p>Find the <code>eth1</code> interface. In this lab environment, <code>eth1</code> is the main network interface we'll use for capturing packets.</p>
</li>
<li><p>Double-click on <code>eth1</code>. This action immediately starts the packet capture process.</p>
</li>
<li><p>Generate some network traffic by opening a new terminal and running:</p>
<pre><code class="lang-bash"> curl www.google.com
</code></pre>
</li>
<li><p>Once you've captured enough packets (aim for at least 20-30 packets), click the red square "Stop" button in the Wireshark toolbar.</p>
</li>
</ol>
<h2 id="heading-understanding-the-wireshark-interface"><strong>Understanding the Wireshark Interface</strong></h2>
<p>The Wireshark interface is divided into three main panels, each with a specific purpose:</p>
<ol>
<li><p><strong>Packet List (top panel)</strong>: This panel shows all the packets that have been captured in the order they were received. It gives you a quick overview of the captured traffic.</p>
</li>
<li><p><strong>Packet Details (middle panel)</strong>: When you select a packet in the top panel, this middle panel shows the details of that packet in a hierarchical format. It breaks down the packet's structure, showing information like the source and destination IP addresses, protocol types, and more.</p>
</li>
<li><p><strong>Packet Bytes (bottom panel)</strong>: This panel displays the raw bytes of the selected packet in hexadecimal format. It's useful for in-depth analysis, especially when you need to look at the exact data being transmitted.</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743386808927/2225da6c-a652-4886-bd7d-f3c94586c688.jpeg" alt="Wireshark Interface" class="image--center mx-auto" width="1726" height="1312" loading="lazy"></p>
<p>To see how these panels work together, click on different packets in the top panel. You'll see the corresponding details and raw bytes update in the middle and bottom panels.</p>
<h2 id="heading-understanding-and-applying-basic-display-filters"><strong>Understanding and Applying Basic Display Filters</strong></h2>
<p>In this step, we're going to explore display filters in Wireshark. Display filters are essential tools when it comes to analyzing network traffic. They help you focus on specific types of packets instead of having to sift through all the captured data.</p>
<p>By the end of this section, you'll know what display filters are, why they're useful, and how to apply basic ones to isolate specific types of network traffic.</p>
<h3 id="heading-what-are-display-filters"><strong>What Are Display Filters?</strong></h3>
<p>When you're analyzing network traffic, looking at every single captured packet can be overwhelming. You usually want to focus on specific types of packets. That's where Wireshark display filters come in. They allow you to show only the packets that meet certain criteria. This makes the analysis process much more efficient because you're not wasting time on irrelevant data.</p>
<p>Display filters in Wireshark use a special syntax. This syntax enables you to filter packets based on various attributes such as protocols, IP addresses, ports, and even the content of the packets. Understanding this syntax is key to effectively using display filters.</p>
<h3 id="heading-filter-toolbar"><strong>Filter Toolbar</strong></h3>
<p>Take a look at the top of the Wireshark window. You'll notice a text field. It might be labeled "Apply a display filter..." or simply show "Expression...". This is the place where you'll enter your display filters. Once you enter a filter and press Enter, Wireshark will use that filter to show only the relevant packets.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743385642595/018e9680-1c29-4168-9d60-975464722447.png" alt="Wireshark Filter Toolbar Location" class="image--center mx-auto" width="1666" height="634" loading="lazy"></p>
<h3 id="heading-basic-protocol-filters"><strong>Basic Protocol Filters</strong></h3>
<p>Let's start with a simple example. Suppose you want to view only HTTP traffic. HTTP is the protocol used for web browsing. To do this, you'll enter a filter in the filter toolbar. Type the following filter and then press Enter:</p>
<pre><code class="lang-plaintext">http
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743385678124/1dff7e49-13c5-439e-aeca-82b461b8727b.png" alt="Wireshark HTTP Filter Output" class="image--center mx-auto" width="2602" height="1192" loading="lazy"></p>
<p>After you apply this filter, Wireshark will only display HTTP packets. All other packets will be temporarily hidden. You'll notice that the filter bar turns green when you apply a valid filter. This is a visual indication that your filter is working correctly.</p>
<p>The output should now show only packets related to HTTP traffic. This typically includes web requests (when you ask a website for information) and responses (when the website sends you the information). If you don't see any HTTP traffic in the sample file, you can try different protocols that might be present, such as TCP, UDP, or DNS:</p>
<pre><code class="lang-plaintext">tcp
</code></pre>
<p>Or try generating more HTTP traffic by running the <code>curl</code> command in a terminal:</p>
<pre><code class="lang-bash">curl www.google.com
</code></pre>
<h3 id="heading-ip-address-filters"><strong>IP Address Filters</strong></h3>
<p>Next, let's filter traffic based on IP addresses. An IP address is like a unique identifier for a device on a network. First, look at your packet list. You'll see columns labeled "Source" and "Destination". These columns show the IP addresses of the devices sending and receiving the packets.</p>
<p>Once you've identified an IP address that appears frequently in your capture (for example, let's say you see <code>192.168.1.1</code>), you can use it to create a filter. Type the following filter in the filter toolbar to see only packets from that source:</p>
<pre><code class="lang-plaintext">ip.src == 192.168.3.131
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743385707141/8719b584-9498-4ecb-bf67-d354906626e0.png" alt="Wireshark IP Address Filter Example" class="image--center mx-auto" width="2602" height="1176" loading="lazy"></p>
<p>You can replace <code>192.168.3.131</code> with an IP address that you actually see in your capture. After applying this filter, only packets with that source IP address will be shown.</p>
<p>If you want to see all the packets again, you can clear the current filter. Just click the "Clear" button (X) on the right side of the filter bar.</p>
<h3 id="heading-port-filters"><strong>Port Filters</strong></h3>
<p>Many network services operate on specific ports. A port is like a door on a device that allows specific types of network traffic to enter or leave. For example, HTTP typically uses port 80.</p>
<p>To filter packets by port number, you can use the following filter:</p>
<pre><code class="lang-plaintext">tcp.port == 80
</code></pre>
<p>This filter will show both incoming and outgoing packets that use TCP port 80. You might also try other common ports like 443 (HTTPS) or 53 (DNS) depending on what's available in your capture.</p>
<h3 id="heading-combining-filters"><strong>Combining Filters</strong></h3>
<p>You can make your filters more powerful by combining them using logical operators like <code>and</code> and <code>or</code>. For example, if you want to show only HTTP traffic that uses port 80, you can use the following filter:</p>
<pre><code class="lang-plaintext">http and tcp.port == 80
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743385736030/8230d965-e822-4341-afa5-35239fbb6975.png" alt="Example of combined filter in Wireshark" class="image--center mx-auto" width="2608" height="1156" loading="lazy"></p>
<p>Try applying different combinations of filters and observe how the displayed packets change. Remember, before trying a new filter, you can either clear the previous one by clicking the "Clear" button or modify the existing filter directly in the filter bar to build upon it.</p>
<h2 id="heading-advanced-filtering-techniques"><strong>Advanced Filtering Techniques</strong></h2>
<p>In this part, we'll explore how to create more sophisticated filters for detailed network traffic analysis. As a beginner, you might wonder why we need advanced filtering. Well, in real-world scenarios, network capture files can be extremely large, filled with all kinds of traffic. Advanced filtering techniques are like a powerful magnifying glass for security professionals. They help us quickly pick out the suspicious or important traffic from the sea of data in these large capture files.</p>
<h3 id="heading-complex-filters-with-multiple-conditions"><strong>Complex Filters with Multiple Conditions</strong></h3>
<p>Wireshark gives you the ability to build complex filters by combining multiple conditions. This is very useful when you want to be more precise in your traffic analysis. Let's start by creating a filter to find HTTP GET requests.</p>
<pre><code class="lang-plaintext">http.request.method == "GET"
</code></pre>
<p>This filter is designed to display only HTTP packets that contain GET requests. When you apply this filter, you'll see packets that are requests sent to web servers. The reason we use this filter is that GET requests are a common type of HTTP request used to retrieve data from a server. By isolating these requests, we can focus on the data retrieval activities in the network.</p>
<p>If your sample file doesn't contain HTTP GET requests, try this alternative filter to find TCP SYN packets which indicate connection attempts:</p>
<pre><code class="lang-plaintext">tcp.flags.syn == 1
</code></pre>
<p>Now, let's make our filter more specific. We'll add a port condition:</p>
<pre><code class="lang-plaintext">tcp.port == 80 and http.request.method == "GET"
</code></pre>
<p>This new filter shows only HTTP GET requests that occur on the standard HTTP port (80). The standard HTTP port is widely used for unencrypted web traffic. By adding this port condition, we're narrowing down our search to only those GET requests that are using the typical HTTP communication channel.</p>
<h3 id="heading-filtering-based-on-packet-size"><strong>Filtering Based on Packet Size</strong></h3>
<p>Network attacks often involve packets with unusual sizes. Attackers might use large or small packets to hide malicious data or to disrupt the normal functioning of the network. To filter based on packet size, we use a specific syntax:</p>
<pre><code class="lang-plaintext">tcp.len &gt;= 100 and tcp.len &lt;= 500
</code></pre>
<p>This filter displays TCP packets with a payload length between 100 and 500 bytes. You can adjust these values according to your needs. For example, if you suspect that an attack involves larger packets, you can increase the upper limit. By filtering based on packet size, we can identify abnormal traffic patterns that might indicate an attack.</p>
<h3 id="heading-filtering-based-on-specific-content"><strong>Filtering Based on Specific Content</strong></h3>
<p>You can also filter traffic based on specific content within packets. This is very useful when you're looking for traffic related to a particular website or service. For example, let's find HTTP traffic related to a specific website.</p>
<pre><code class="lang-plaintext">http.host contains "google"
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743385773497/36b7bc2e-b7e9-4b68-9dc7-82e429c5ea01.png" alt="Wireshark HTTP Host Filter" class="image--center mx-auto" width="2618" height="1168" loading="lazy"></p>
<p>This filter shows only HTTP traffic where the host header contains "google". You can replace "google" with any domain you're interested in analyzing. The host header in an HTTP request tells the server which website the client is trying to access. By filtering based on the host header, we can focus on the traffic related to a specific domain.</p>
<p>If your sample file doesn't have HTTP traffic with host headers, try this more general content filter:</p>
<pre><code class="lang-plaintext">frame contains "http"
</code></pre>
<h3 id="heading-using-the-contains-operator-for-text-searching"><strong>Using the "contains" Operator for Text Searching</strong></h3>
<p>The <code>contains</code> operator is a handy tool for searching for specific text strings in packets. It allows us to look for certain keywords within the packet data.</p>
<pre><code class="lang-plaintext">frame contains "password"
</code></pre>
<p>This filter shows packets containing the word "password" anywhere in the packet data. This can be very helpful for detecting possible security issues. For example, if passwords are being sent in clear text (which is a big security risk), this filter can help us spot those packets.</p>
<p>Or try this filter:</p>
<pre><code class="lang-plaintext">frame contains "login"
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743385793822/024b4482-0b5d-4b20-985c-6263bd7f48d6.png" alt="Wireshark Password Filter Example" class="image--center mx-auto" width="2612" height="1164" loading="lazy"></p>
<h3 id="heading-negating-filters"><strong>Negating Filters</strong></h3>
<p>Sometimes, you might want to see all the traffic except for certain types. That's where the <code>not</code> operator comes in.</p>
<pre><code class="lang-plaintext">not arp
</code></pre>
<p>This filter hides all ARP packets. ARP (Address Resolution Protocol) is used to map IP addresses to MAC addresses in a local network. Sometimes, ARP traffic can be very common and might clutter your analysis. By using the <code>not</code> operator, you can exclude this type of traffic and focus on other more relevant packets.</p>
<h3 id="heading-saving-and-applying-filter-bookmarks"><strong>Saving and Applying Filter Bookmarks</strong></h3>
<p>If you find yourself using certain filters frequently, you don't have to type them in every time. You can save them as bookmarks. Here's how:</p>
<ol>
<li><p>Enter a filter in the filter bar. This is where you type in the filter expressions we've been learning about.</p>
</li>
<li><p>Click the "+" button on the right side of the filter bar. This button is used to save the current filter as a bookmark.</p>
</li>
<li><p>Give your filter a name and click "OK". Naming the filter makes it easy to identify later.</p>
</li>
</ol>
<p>Once you've saved your filter, you can apply it by clicking on its name in the filter dropdown menu. This saves you time and effort, especially when you're doing repeated analysis.</p>
<h3 id="heading-exporting-filtered-packets"><strong>Exporting Filtered Packets</strong></h3>
<p>After you've filtered your traffic to show only the packets of interest, you might want to save just these packets to a new file. This is useful for sharing specific findings with colleagues or for further analysis. Here's how you do it:</p>
<ol>
<li><p>Apply your desired filter. Make sure you've set up the filter to show only the packets you want to save.</p>
</li>
<li><p>Click on File &gt; Export Specified Packets. This option allows you to export a specific set of packets.</p>
</li>
<li><p>Make sure "Displayed" is selected in the Packet Range section. This ensures that only the packets that are currently visible (that is, the ones that match your filter) are exported.</p>
</li>
<li><p>Choose a filename and location. This is where you decide where to save the new capture file and what to name it.</p>
</li>
<li><p>Click "Save". This creates a new capture file containing only the packets that matched your filter.</p>
</li>
</ol>
<h2 id="heading-analyzing-security-related-traffic"><strong>Analyzing Security-Related Traffic</strong></h2>
<p>In this step, we're going to focus on using Wireshark filters for security analysis. Security analysis is crucial in the world of cybersecurity as it helps us spot potentially malicious activities in network traffic. By the end of this section, you'll be able to identify various types of security threats using specific Wireshark filters.</p>
<h3 id="heading-identifying-port-scanning-activities"><strong>Identifying Port Scanning Activities</strong></h3>
<p>Port scanning is a common technique used by attackers to gather information about a target system. Attackers use it to find open ports on a network, which they can then exploit.</p>
<p>To detect potential port scanning, we look for a large number of connection attempts from a single source to multiple ports.</p>
<p>Let's use a specific filter to identify such activities. Try this filter in Wireshark:</p>
<pre><code class="lang-plaintext">tcp.flags.syn == 1 and tcp.flags.ack == 0
</code></pre>
<p>This filter shows SYN packets without the ACK flag. In a TCP connection, the SYN packet is the first one sent to initiate a connection, and the ACK packet is used to acknowledge the connection. When we see a lot of SYN packets without ACK from one source to different destination ports, it's a strong indication of port scanning.</p>
<h3 id="heading-detecting-suspicious-dns-traffic"><strong>Detecting Suspicious DNS Traffic</strong></h3>
<p>DNS tunneling and other DNS-based attacks are becoming more common. These attacks use the DNS protocol to hide malicious activities, such as data exfiltration or command and control communication. To detect such attacks, we need to look for unusual DNS traffic.</p>
<p>Use this filter to examine DNS queries:</p>
<pre><code class="lang-plaintext">dns
</code></pre>
<p>Once you apply this filter, look for unusually long domain names or a high volume of DNS requests to the same domain. These could be signs of data exfiltration or command and control communication.</p>
<h3 id="heading-identifying-password-brute-force-attempts"><strong>Identifying Password Brute Force Attempts</strong></h3>
<p>Password brute force attacks are a common way for attackers to gain unauthorized access to services like SSH or FTP. In a brute force attack, the attacker tries multiple password combinations until they find the correct one.</p>
<p>To detect potential brute force password attempts, we can filter for failed login attempts. Use this filter:</p>
<pre><code class="lang-plaintext">ftp contains "530" or ssh contains "Failed"
</code></pre>
<p>This filter shows FTP and SSH packets that contain common failure response messages. If you see multiple failures from the same source, it may indicate a brute force attempt.</p>
<h3 id="heading-analyzing-http-error-responses"><strong>Analyzing HTTP Error Responses</strong></h3>
<p>Web application attacks often generate HTTP error responses. Attackers may try to exploit vulnerabilities in web applications, and these attempts can result in error responses from the server.</p>
<p>Filter for these error responses with:</p>
<pre><code class="lang-plaintext">http.response.code &gt;= 400
</code></pre>
<p>This filter shows HTTP response packets with status codes of 400 or higher. All these status codes represent error responses. By examining these packets, we can identify attempted web exploits.</p>
<h3 id="heading-finding-clear-text-credentials"><strong>Finding Clear-Text Credentials</strong></h3>
<p>Transmitting credentials in clear text is a major security risk. If an attacker intercepts these credentials, they can gain unauthorized access to the system.</p>
<p>To detect clear-text credentials, use this filter:</p>
<pre><code class="lang-plaintext">http contains "user" or http contains "pass" or http contains "login"
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743385832534/4c38654f-8ff0-4bc3-8bc6-0dd1161dc0f1.png" alt="Wireshark Clear-Text Cred Filter" class="image--center mx-auto" width="2620" height="1244" loading="lazy"></p>
<p>This filter helps us find HTTP traffic that might contain login information. Carefully examine the packets that match this filter to identify potential security risks.</p>
<h2 id="heading-analyzing-sample-traffic-and-generating-new-traffic"><strong>Analyzing Sample Traffic and Generating New Traffic</strong></h2>
<p>Now that you've learned various security-focused filters, it's time to put your knowledge into practice. You can either analyze the provided sample file or generate and analyze new traffic.</p>
<h3 id="heading-analyzing-the-sample-file"><strong>Analyzing the Sample File</strong></h3>
<p>If you're using the provided sample file (<code>/home/labex/project/sample.pcapng</code>), try applying some of the security filters we've discussed to identify any interesting patterns:</p>
<pre><code class="lang-plaintext">tcp.flags.syn == 1 and tcp.flags.ack == 0
</code></pre>
<p>Look for patterns that might indicate scanning, suspicious connections, or other security concerns.</p>
<h3 id="heading-generating-and-analyzing-new-traffic"><strong>Generating and Analyzing New Traffic</strong></h3>
<p>Alternatively, open a new terminal window. In this window, we'll generate some HTTP traffic with multiple requests. Run the following commands:</p>
<pre><code class="lang-bash"><span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> {1..5}; <span class="hljs-keyword">do</span>
  curl -I www.google.com
  sleep 1
<span class="hljs-keyword">done</span>
</code></pre>
<p>These commands send five HTTP HEAD requests to <code>www.google.com</code> with a one-second interval between each request.</p>
<p>Next, go to Wireshark and apply this filter to find all HTTP requests:</p>
<pre><code class="lang-plaintext">http.request
</code></pre>
<p>This filter will show all the HTTP requests in the captured traffic.</p>
<p>Look through these packets to identify patterns of normal HTTP traffic. Notice the headers, the frequency of requests, and other details.</p>
<p>Finally, try to create a filter that can distinguish normal HTTP browsing from automated scanning tools. For example:</p>
<pre><code class="lang-plaintext">http.request and !(http.user_agent contains "Mozilla")
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743385858895/3feb916b-39ac-4ced-ab76-8597186cbbf0.png" alt="Wireshark HTTP User Agent Filter" class="image--center mx-auto" width="2610" height="1184" loading="lazy"></p>
<p>This filter shows HTTP requests that don't have browser user agents. Since most normal web browsing is done using browsers with Mozilla in the user agent, requests without it might indicate automated tools rather than normal browsing.</p>
<p>By practicing these security-focused filtering techniques, you'll develop the skills needed to quickly identify suspicious traffic in real-world network captures.</p>
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>In this tutorial, you have learned how to use Wireshark display filters for network traffic analysis and potential security threat identification.</p>
<p>You began by either working with a provided sample capture file or capturing live network traffic and familiarizing yourself with the Wireshark interface. Then, you mastered basic display filters to isolate specific traffic types according to protocols, IP addresses, and ports. You also advanced your skills with complex filtering techniques, combining multiple conditions and searching for specific content. Finally, you applied these skills in security analysis scenarios to detect suspicious activities such as port scanning, credential exposure, and potential attacks.</p>
<p>These Wireshark filtering skills are crucial for efficient network troubleshooting and security analysis. By quickly isolating relevant packets from large captures, you can greatly reduce the time required to identify and respond to network issues and security incidents.</p>
<p>As you keep practicing with Wireshark, you will gain an intuitive understanding of network protocols and traffic patterns, enhancing your overall cybersecurity capabilities.</p>
<blockquote>
<p>To practice the operations from this tutorial, try the interactive hands-on lab: <a target="_blank" href="https://labex.io/labs/wireshark-analyze-network-traffic-with-wireshark-display-filters-415944?course=quick-start-with-wireshark">Analyze Network Traffic with Wireshark Display Filters</a></p>
</blockquote>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Learn User Management in RHEL: A Comprehensive Guide ]]>
                </title>
                <description>
                    <![CDATA[ Imagine you're throwing a house party. You wouldn’t hand out keys to every guest, right? Some friends can roam freely, some should probably stick to the living room, and a few—well, let’s just say they need supervision. Managing users in RHEL is kind... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/learn-user-management-in-rhel-a-comprehensive-guide/</link>
                <guid isPermaLink="false">67b5da0a6db178277c2bebc9</guid>
                
                    <category>
                        <![CDATA[ Linux ]]>
                    </category>
                
                    <category>
                        <![CDATA[ RHEL ]]>
                    </category>
                
                    <category>
                        <![CDATA[ user management ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Tanishka Makode ]]>
                </dc:creator>
                <pubDate>Wed, 19 Feb 2025 13:18:02 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1739971027992/d19c4616-4c2e-4cc4-ac45-384e6520d1a8.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Imagine you're throwing a house party. You wouldn’t hand out keys to every guest, right? Some friends can roam freely, some should probably stick to the living room, and a few—well, let’s just say they need supervision.</p>
<p>Managing users in RHEL is kind of like that. You decide who gets in, what they can do, and how much control they have. Without proper management, things can get messy fast—like that friend who somehow DJs when no one asks.</p>
<p>So, let’s dive into user management and ensure your Linux system stays organized, secure, and drama-free! 🚀</p>
<h2 id="heading-table-of-contents">Table Of Contents</h2>
<ol>
<li><p><a class="post-section-overview" href="#heading-what-is-a-user-in-linux">What is a User in Linux?</a></p>
<ul>
<li><a class="post-section-overview" href="#heading-understanding-sudo-in-user-management">Understanding sudo in User Management</a></li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-user-management-commands-in-linux">User Management Commands in Linux</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-how-to-add-a-user">How to Add a User</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-check-if-a-user-is-created">How to Check if a User is Created</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-assign-a-password">How to Assign a Password</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-switch-users">How to Switch Users</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-understanding-groups-in-linux">Understanding Groups in Linux</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-modify-users">How to Modify Users</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-final-words">Final Words</a></p>
</li>
</ol>
<h2 id="heading-what-is-a-user-in-linux"><strong>What is a User in Linux?</strong></h2>
<p>A user in Linux is an account that allows someone (or a process) to interact with the system. Since Linux is a multi-user operating system, multiple users can exist on the same system, each with their own settings, files, and permissions. Users can have different levels of permissions, which determine what they can access or modify on the system.</p>
<p>Linux categorizes users into three main types based on their roles and privileges:</p>
<ol>
<li><p>Privileged Users: These users have unrestricted access to the entire system. They have the highest level of permissions and can perform any operation on the system. They can install/remove software, modify system files, create/manage users, and even delete everything. These users are also called root users.</p>
</li>
<li><p>System Users: The system creates these users to run background processes or services. They can’t login like a normal user. Their sole purpose is to manage system operations like databases, web servers and scheduled tasks.</p>
</li>
<li><p>Normal Users: These are the everyday users created by administrators or during system installation. They have their home directory and can store personal files and settings. They can’t modify system files but can execute tasks within their permission scope.</p>
</li>
</ol>
<h3 id="heading-understanding-sudo-in-user-management">Understanding <code>sudo</code> in User Management</h3>
<p>The <code>sudo</code> (Superuser Do) command allows a regular user to execute administrative tasks with elevated privileges. Since user management tasks—such as adding, modifying, or deleting users—require root access, normal users must use <code>sudo</code> before these commands.</p>
<p>Note that the following commands are executed as the root user. If you are using a normal user account, you must prefix them with <code>sudo</code> to perform user management tasks.</p>
<p>Now let’s see how we manage users on RHEL.</p>
<h2 id="heading-user-management-commands-in-linux">User Management Commands in Linux</h2>
<h3 id="heading-how-to-add-a-user">How to add a user</h3>
<p>To create a new user account, use following command:</p>
<p>Syntax:</p>
<pre><code class="lang-bash">useradd [user_name]
</code></pre>
<p>Example:</p>
<pre><code class="lang-bash">useradd Tanishka <span class="hljs-comment"># Root user</span>
sudo useradd Tanishka <span class="hljs-comment"># Normal user</span>
</code></pre>
<p>Once you create a user, you can verify its existence in the <code>/etc/passwd</code> file. This file stores essential user account information (but <strong>not passwords</strong>, despite the name).</p>
<h4 id="heading-how-to-check-if-a-user-is-created">How to check if a user is created</h4>
<p>To confirm the user entry in <code>/etc/passwd</code>, use one of the following methods:</p>
<ol>
<li>View the file using <code>cat</code> or <code>grep</code></li>
</ol>
<pre><code class="lang-bash">cat /etc/passwd <span class="hljs-comment"># Displays entire file content</span>
grep Tanishka /etc/passwd <span class="hljs-comment"># Displays information about Tanishka user only</span>
</code></pre>
<ol start="2">
<li>Use id command:</li>
</ol>
<p>The <code>id</code> command is used to display a user’s <strong>UID (User ID), GID (Group ID), and the groups they belong to</strong>. It helps in verifying user information and checking permissions.</p>
<pre><code class="lang-bash">id Tanishka
<span class="hljs-comment"># Displays user id of Tanishka,</span>
<span class="hljs-comment"># hence verifying user has been created</span>
</code></pre>
<p>Let’s understand what’s going on in the /etc/password fields. Each line in <code>/etc/passwd</code> represents a user account and contains seven fields separated by colons (<code>:</code>):</p>
<pre><code class="lang-bash">username:x:UID:GID:comment:home_directory:shell
</code></pre>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Field</strong></td><td><strong>Description</strong></td></tr>
</thead>
<tbody>
<tr>
<td>username</td><td>Name of the user (for example, john, admin).</td></tr>
<tr>
<td>x</td><td>Placeholder for the password (actual password is stored in /etc/shadow).</td></tr>
<tr>
<td>UID</td><td>User ID (for example, 1001 for a normal user, 0 for root).</td></tr>
<tr>
<td>GID</td><td>Group ID (primary group of the user).</td></tr>
<tr>
<td>comment</td><td>Optional user description (for example, full name or other info).</td></tr>
<tr>
<td>home_directory</td><td>User’s home directory (for example /home/john).</td></tr>
<tr>
<td>shell</td><td>The default shell assigned to the user (for example, /bin/bash, /bin/sh, /usr/sbin/nologin).</td></tr>
</tbody>
</table>
</div><h3 id="heading-how-to-assign-a-password">How to Assign a Password</h3>
<p>Once an account is created, it’s essential to assign a password to the account. Otherwise, that account can’t be logged in through a GUI login interface. To give a password to a user account, user this command:</p>
<p>Syntax:</p>
<pre><code class="lang-bash">passwd [user_name]
</code></pre>
<p>Example:</p>
<pre><code class="lang-bash">passwd Tanishka
</code></pre>
<p>You will be prompted to enter the password. Enter the password and you’re all set! Even though user information is stored in /etc/passwd file, actual information about the password is stored in the /etc/shadow file (weird, I know…).</p>
<p>To see the content of the /etc/shadow file, use this command:</p>
<pre><code class="lang-bash">cat /etc/shadow
</code></pre>
<p>Each line in <code>/etc/shadow</code> represents a user account password and contains nine fields separated by colons (<code>:</code>):</p>
<pre><code class="lang-bash">username:password:lastchg:min:max:warn:inactive:expire:reserved
</code></pre>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Field</td><td>Description</td></tr>
</thead>
<tbody>
<tr>
<td>username</td><td>User’s login name</td></tr>
<tr>
<td>password</td><td>Encrypted password or password status (for example, locked)</td></tr>
<tr>
<td>lastchg</td><td>Last password change (days since Jan 1, 1970)</td></tr>
<tr>
<td>min</td><td>Minimum days between password changes</td></tr>
<tr>
<td>max</td><td>Maximum days before password change is required</td></tr>
<tr>
<td>warn</td><td>Warning period before password expiration</td></tr>
<tr>
<td>inactive</td><td>Inactive period after password expiration</td></tr>
<tr>
<td>expire</td><td>Account expiration date (days since Jan 1, 1970)</td></tr>
<tr>
<td>reserved</td><td>Reserved for future use</td></tr>
</tbody>
</table>
</div><p>To change password aging information, you use the <code>chage</code> (short for change age) command like this:</p>
<p>Syntax:</p>
<pre><code class="lang-bash">chage [OPTIONS] [user_name]
</code></pre>
<p>Example:</p>
<pre><code class="lang-bash">chage -l tanishka <span class="hljs-comment"># Lists the current password aging information</span>
chage -m 10 tanishka <span class="hljs-comment"># Sets the minimum days to change password</span>
chage -M 10 tanishka <span class="hljs-comment"># Sets the maximum days password must be changed</span>
chage -W 7 tanishka <span class="hljs-comment"># Sets the number of days before the password expires that the user will be warned to change the password</span>
chage -I 10 tanishka <span class="hljs-comment"># Sets the number of days after password expiration that the account will be disabled if not logged in</span>
chage -E 2025-12-31 tanishka <span class="hljs-comment"># Sets the date when the user account will expire </span>
chage -d 2024-12-25 tanishka <span class="hljs-comment"># Sets the last password change date</span>
</code></pre>
<p>Now that you have learned to create users and assign passwords, you need to know how to switch between users. Let’s see that now.</p>
<h3 id="heading-how-to-switch-users">How to Switch Users</h3>
<p>The <code>su</code> (Substitute User) command allows you to <strong>switch from one user to another</strong> without logging out of the current session.</p>
<p>Syntax:</p>
<pre><code class="lang-bash">su - [user_name]
</code></pre>
<p>Example:</p>
<pre><code class="lang-bash">su - Tanishka <span class="hljs-comment"># Switches to Tanishka user</span>
</code></pre>
<ul>
<li><p><code>su</code> stands for "substitute user" (or "switch user").</p>
</li>
<li><p>The <code>-</code> (hyphen) loads the target user's full environment, including their shell, path, and profile settings (similar to logging in as that user).</p>
</li>
<li><p>If no username is provided, it switches to the root user by default.</p>
</li>
</ul>
<p>To return to original or root user, simply enter ‘exit’.</p>
<h3 id="heading-understanding-groups-in-linux">Understanding Groups in Linux</h3>
<p>Just like a party where guests can belong to different social circles, Linux groups allow users to be part of different permission levels. Groups help manage file access, system privileges, and administrative controls efficiently.</p>
<p>Linux has two types of groups:</p>
<p><strong>1. Primary Group:</strong></p>
<ul>
<li><p>Every user has one primary group.</p>
</li>
<li><p>When a user creates a new file, it belongs to their primary group.</p>
</li>
<li><p>It is usually named the same as the username.</p>
</li>
</ul>
<p><strong>2. Secondary Groups:</strong></p>
<ul>
<li><p>A user can belong to multiple secondary groups.</p>
</li>
<li><p>These groups provide additional permissions beyond the primary group.</p>
</li>
<li><p>Users can be assigned to various secondary groups to access shared resources.</p>
</li>
</ul>
<p>To check a user’s group membership:</p>
<pre><code class="lang-bash">id [user_name]
</code></pre>
<p>This displays the user’s UID, primary group (GID), and any secondary groups they belong to.</p>
<p>To add a new group:</p>
<pre><code class="lang-bash">groupadd [group_name]
</code></pre>
<h3 id="heading-how-to-modify-a-user">How to Modify a User</h3>
<p>Sometimes, you might need to update user details, such as changing usernames, user IDs, group memberships, home directories, or login shells. You use the <code>usermod</code> command to modify existing user accounts while preserving their files and configurations.</p>
<p>Syntax:</p>
<pre><code class="lang-bash">usermod [OPTIONS] [user_name]
</code></pre>
<p>Let’s break down the different options available for modifying user accounts.</p>
<ol>
<li><strong>Change the username</strong></li>
</ol>
<p>If you want to rename an existing user, use the <code>-l</code> option:</p>
<p>Syntax:</p>
<pre><code class="lang-bash">usermod -l new_username old_username
</code></pre>
<p>Example:</p>
<pre><code class="lang-bash">usermod -l tanishkamakode tanishka
</code></pre>
<p>This renames <code>tanishka</code> to <code>tanishkamakode</code>. Just keep in mind that the home directory remains the same (<code>/home/tanishka</code>), so you might need to rename it manually.</p>
<p>To rename the home directory as well, use:</p>
<pre><code class="lang-bash">mv /home/tanishka /home/tanishkamakode
</code></pre>
<ol start="2">
<li><strong>Change the user id:</strong></li>
</ol>
<p>Each user has a unique User ID (UID). If you need to change it, use <code>-u</code>.</p>
<p>Syntax:</p>
<pre><code class="lang-bash">usermod -u new_UID user_name
</code></pre>
<p>Example:</p>
<pre><code class="lang-bash">usermod -u 2001 tanishka
</code></pre>
<p>This changes <code>tanishka</code>'s UID to <code>2001</code>. Before you do this, you’ll want to <strong>make sure that no other user has the same UID.</strong> This is important.</p>
<p>If the user owns files under the old UID, you should update them after changing the UID.</p>
<ol start="3">
<li><strong>Change the primary group</strong></li>
</ol>
<p>Every user belongs to a primary group. To change it, use <code>-g</code>.</p>
<p>Syntax:</p>
<pre><code class="lang-bash">usermod -g new_group user_name
</code></pre>
<p>Example:</p>
<pre><code class="lang-bash">usermod -g developers tanishka
</code></pre>
<p>This changes <code>tanishka</code>'s primary group to <code>developers</code>. Just keep in mind that <code>usermod -g developers tanishka</code> <strong>removes</strong> the user from all secondary groups. To avoid that, just make sure you check and re-add secondary groups as needed.</p>
<p>Also, the group must exist beforehand. To create a group, run this command:</p>
<p>Syntax:</p>
<pre><code class="lang-bash">groupadd [group_name]
</code></pre>
<p>Example:</p>
<pre><code class="lang-bash">groupadd developers
</code></pre>
<p>Now, to check tanishka’s group, do the following:</p>
<pre><code class="lang-bash">id tanishka
</code></pre>
<ol start="4">
<li><strong>Add to a secondary group</strong></li>
</ol>
<p>A user can belong to multiple secondary groups. Use <code>-G</code> to assign them.</p>
<p>Syntax:</p>
<pre><code class="lang-bash">usermod -G group1,group2 user_name
</code></pre>
<p>Example:</p>
<pre><code class="lang-bash">usermod -G linux,docker tanishka
</code></pre>
<p>This adds <code>tanishka</code> to the <code>sudo</code> and <code>docker</code> groups. Just keep in mind that this <strong>replaces</strong> any existing secondary groups that the user might already belong to. To add groups without removing the current ones, use <code>-aG</code> (append to groups) like this:</p>
<pre><code class="lang-bash">usermod -aG linux,docker tanishka
</code></pre>
<ol start="5">
<li><strong>Change the home directory:</strong></li>
</ol>
<p>You can change a user’s default home directory using <code>-d</code>.</p>
<p>Syntax:</p>
<pre><code class="lang-bash">usermod -d /new/home_directory user_name
</code></pre>
<p>Example:</p>
<pre><code class="lang-bash">usermod -d /home/tani tanishka
</code></pre>
<p>This sets <code>tanishka</code>'s home directory to <code>/home/tani</code>, but <strong>it does not move existing files</strong>. To move them, add the <code>-m</code> option:</p>
<pre><code class="lang-bash">usermod -d /home/tani -m tanishka
</code></pre>
<p>After moving the home directory, just make sure you’ve updated file ownership.</p>
<ol start="6">
<li><strong>Change the login shell:</strong></li>
</ol>
<p>The default shell for a user can be changed using <code>-s</code>.</p>
<p>Syntax:</p>
<pre><code class="lang-bash">usermod -s /new/shell user_name
</code></pre>
<p>Example:</p>
<pre><code class="lang-bash">usermod -s /bin/zsh tanishka
</code></pre>
<p>This changes <code>tanishka</code>'s default shell to <code>zsh</code>. Common shells include:</p>
<ul>
<li><p><code>/bin/bash</code> (default)</p>
</li>
<li><p><code>/bin/sh</code></p>
</li>
<li><p><code>/bin/zsh</code></p>
</li>
<li><p><code>/usr/sbin/nologin</code> (to disable login)</p>
</li>
</ul>
<p>With <code>usermod</code>, you can fine-tune user settings to match system requirements. Always check changes using:</p>
<pre><code class="lang-bash">id tanishka
grep tanishka /etc/passwd
</code></pre>
<h2 id="heading-final-words">Final Words</h2>
<p>In this article, we explored the fundamentals of user management in RHEL, a crucial aspect of system administration. We started with creating and managing users, then moved on to handling groups.</p>
<p>If you're new to Linux and want to build a strong foundation, check out my first tutorial on <a target="_blank" href="https://www.freecodecamp.org/news/guide-to-rhel-linux-basics/">Basic Linux Commands</a>, where I cover essential commands every beginner should know. You can also read my second tutorial on <a target="_blank" href="https://www.freecodecamp.org/news/how-to-use-the-vim-text-editor-intro-for-devs/">Vim</a> to learn how to navigate and edit text efficiently in this powerful editor. These articles will complement what you’ve learned about user management here.</p>
<p>Keep practicing these commands, and soon they’ll become second nature to you. Mastery comes with repetition, so continue experimenting and applying these fundamentals in real-world scenarios.</p>
<p>Stay tuned for more articles. Get ready to take your RHEL skills to the next level.</p>
<p><a target="_blank" href="https://linktr.ee/tanishkamakode">Let’s connect!</a></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Use the Vim Text Editor – An Introduction for Developers ]]>
                </title>
                <description>
                    <![CDATA[ Imagine a carpenter without tools, a writer without a pen, or a chef without a knife—this is like trying to imagine a developer or sysadmin without a reliable text editor. For devs, text editors are the ultimate multitools, shaping how we create, man... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-use-the-vim-text-editor-intro-for-devs/</link>
                <guid isPermaLink="false">67a24bb37e501febb084c852</guid>
                
                    <category>
                        <![CDATA[ vim ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Linux ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Text Editors ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Tanishka Makode ]]>
                </dc:creator>
                <pubDate>Tue, 04 Feb 2025 17:17:39 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1738684583892/739ec0fa-e8a2-4f08-a265-7fa5034c932d.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Imagine a carpenter without tools, a writer without a pen, or a chef without a knife—this is like trying to imagine a developer or sysadmin without a reliable text editor.</p>
<p>For devs, text editors are the ultimate multitools, shaping how we create, manage, and transform raw data into meaningful output.</p>
<p>While modern editors like VS Code and Sublime Text have gained popularity for their sleek interfaces, there’s something timeless about the simplicity and power of classic tools.</p>
<p>Loved by some and feared by others, Vim is a text editor that has stood the test of time. Born from its predecessor Vi, Vim (Vi Improved) offers unparalleled speed, versatility, and control.</p>
<p>In this tutorial, you’ll learn what makes Vim so special. We’ll explore its commands, text filtering, and string manipulation capabilities to help you harness its true power.</p>
<h2 id="heading-what-well-cover">What we’ll cover:</h2>
<ol>
<li><p><a class="post-section-overview" href="#heading-text-editors-in-linux">Text Editors in Linux</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-open-the-vim-editor">How to Open the Vim Editor</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-modes-in-vim">Modes in Vim</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-basic-vim-commands">Basic Vim Commands</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-cut-copy-paste-and-delete-commands">Cut, Copy, Paste, and Delete Commands</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-search-and-replace-commands">Search and Replace Commands</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-read-files-using-more-and-less">How to Read Files using more and less</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-text-filters">Text Filters</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-text-summarization-tools-wc">Text Summarization Tool: wc</a></p>
</li>
</ol>
<h2 id="heading-text-editors-in-linux">Text Editors in Linux</h2>
<p>Linux provides a variety of text editors, each designed for different types of users – from beginners to advanced developers.</p>
<p>Editors like <strong>Nano</strong> are great for newcomers who need a simple, user-friendly experience in the terminal. Nano displays helpful commands at the bottom of the screen, making it easy to navigate without a steep learning curve.</p>
<p><strong>Gedit</strong>, the default editor for the GNOME desktop environment, offers a clean, graphical interface ideal for basic text editing. On the other hand, <strong>Kate</strong> caters to KDE desktop users and provides a more feature-rich experience, with multiple windows, syntax highlighting, and an integrated terminal.</p>
<p><strong>Emacs</strong> is a versatile and highly customizable editor that can be turned into an entire development environment, ideal for power users who want more than just a text editor.</p>
<p><strong>VS Code</strong> and <strong>Atom</strong> are modern, graphical editors that offer a rich set of features, including extensions, debugging tools, and Git integration, making them favorites among developers.</p>
<h3 id="heading-why-do-many-devs-prefer-vim">Why Do Many Devs Prefer Vim?</h3>
<p>Despite the wide range of text editors available in Linux, Vim stands out as the preferred choice for many users, especially those who need a lightweight, fast, and highly efficient editing environment.</p>
<p>Vim, an improved version of the classic Vi editor, is available on nearly every Linux distribution and can be used in both graphical and terminal-based environments. Its popularity stems from its exceptional speed and efficiency.</p>
<p>Vim is entirely keyboard-driven, allowing you to perform complex editing tasks quickly without the need for a mouse. This makes it incredibly useful for remote work, where you may have to rely on minimal system resources.</p>
<p>The power of Vim lies in its <strong>modal editing</strong> system, which separates the text input and command modes, which you’ll learn soon. This lets you execute precise actions with a few keystrokes. Whether you're navigating a file, searching for a string, or performing complex text manipulations, Vim enables you to do it all without taking your hands off the keyboard.</p>
<p>Because Vim (or Vi) is pre-installed on most Linux systems, it’s often the go-to option for developers and system administrators, who rely on its ubiquity and powerful features. In short, Vim’s combination of speed, versatility, and efficiency makes it the editor of choice for many Linux users looking to boost their productivity.</p>
<h2 id="heading-how-to-open-the-vim-editor">How to Open the Vim Editor</h2>
<p>Opening a file in Vim is straightforward and efficient. To start editing any file, simply use the following command in your terminal:</p>
<pre><code class="lang-bash">vim [filename]
</code></pre>
<p>Here, replace <code>[filename]</code> with the name of the file you want to open. If the file doesn't exist, Vim will create a new file with that name. Once executed, Vim will open the file and allow you to start editing right away.</p>
<p>Example:</p>
<pre><code class="lang-bash">vim data.txt <span class="hljs-comment"># A file that doesn't exist yet, so Vim creates a new file named data.txt</span>
</code></pre>
<p>When you execute the command <code>vim data.txt</code>, Vim opened a new file named <code>data.txt</code> because a file with this name did not previously exist in the current directory. In Vim, this is indicated by the message at the bottom of the editor, which reads: <code>data.txt [NEW]</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736790675984/98007e6c-cc90-42be-b853-f239f4f2e819.png" alt="Creating a new file using vim command" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>If the file you want to edit already exists, you can open it in Vim by using the same command. You’ll be able to see its contents and make edits as needed. If you don’t see the <code>[New]</code> message at the bottom (as shown for new files), it confirms the file already exists.</p>
<h2 id="heading-modes-in-vim">Modes in Vim</h2>
<p>Vim has several modes, but the most commonly used ones are:</p>
<ul>
<li><p><strong>Normal Mode (Command Mode)</strong> – Used for navigation and executing commands.</p>
</li>
<li><p><strong>Insert Mode</strong> – Used for typing and editing text.</p>
</li>
</ul>
<p>When you open a file in Vim, it starts in Command Mode by default. This mode allows you to navigate, execute commands, and perform various operations without directly modifying the text. To edit the text in the file, you need to switch to Insert Mode.</p>
<h3 id="heading-what-is-command-mode"><strong>What is Command Mode?</strong></h3>
<p>Command Mode is the default mode in Vim. In this mode, you can navigate through the file using the arrow keys and cut, copy, paste, or delete the content and execute commands like saving or quitting.</p>
<p>To switch to Command Mode from any other mode, press the <code>Esc</code> key.</p>
<p>Example: If you are in Insert Mode and need to return to Command Mode to save or navigate, press <code>Esc</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736791773096/298ef4f6-6fba-493d-b05c-0564290862a1.png" alt="Vim in command mode" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>In the above image, "hello.txt" 1L, 1B” indicates that the file <code>hello.txt</code> is open and is currently in Command Mode, which is the default mode when you open Vim. 1L Represents 1 line in the file (currently the file is empty, so there’s just one blank line). 1B Represents 1 byte (the file is currently empty)</p>
<h3 id="heading-what-is-insert-mode"><strong>What is Insert Mode?</strong></h3>
<p>Insert Mode allows you to edit or type text in the file, similar to a traditional text editor. You can insert new lines, modify existing text, and make changes directly.</p>
<p>Press <code>i</code> while in Command Mode. This switches to Insert Mode and places the cursor at the current position, allowing you to start typing.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736791793215/93d1ff39-9015-4209-8903-e29d1ccfb7c1.png" alt="Editor switched to INSERT mode" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>In the above image, “—INSERT—” indicates that the editor has been switched to Insert Mode, allowing you to type and edit text directly in the file.</p>
<p>Quick glance: When you open a file, always check the bottom of the terminal to determine your current mode. If the bottom line displays file-related information, you are in Command Mode. If the bottom line explicitly says <code>-- INSERT --</code>, you are in Insert Mode. If you want to go from <strong>Command Mode to Insert Mode:</strong> Press <code>i</code>. And from <strong>Insert Mode to Command Mode:</strong> Press <code>Esc</code>.</p>
<h2 id="heading-basic-vim-commands">Basic Vim Commands</h2>
<p>Below are some essential commands to help you manage files efficiently in Vim.</p>
<p><strong>Note:</strong> Before using these commands, ensure you're in command mode by pressing <code>Esc</code>.</p>
<h3 id="heading-1-save-changes"><strong>1. Save Changes</strong></h3>
<p>To save the changes made to a file, use the following command:</p>
<pre><code class="lang-bash">:w
</code></pre>
<p>This writes (saves) the current file without exiting Vim.</p>
<h3 id="heading-2-save-changes-and-quit"><strong>2. Save Changes and Quit</strong></h3>
<p>If you're done editing and want to save changes and exit Vim simultaneously, use:</p>
<pre><code class="lang-bash">:wq
</code></pre>
<p>This command writes the changes and then quits the editor.</p>
<h3 id="heading-3-quit-without-saving-changes"><strong>3. Quit Without Saving Changes</strong></h3>
<p>If you wish to exit without saving any changes, you can use:</p>
<pre><code class="lang-bash">:q
</code></pre>
<p>This command will close the file if no changes have been made since the last save.</p>
<h3 id="heading-4-force-quit-without-saving-changes"><strong>4. Force Quit Without Saving Changes</strong></h3>
<p>In case you've made changes to the file but want to exit without saving them, you can force quit with:</p>
<pre><code class="lang-bash">:q!
</code></pre>
<p>The <code>!</code> overrides any unsaved changes and closes the file immediately.</p>
<h2 id="heading-cut-copy-paste-and-delete-commands-plus-others"><strong>Cut, Copy, Paste, and Delete Commands (Plus Others)</strong></h2>
<h3 id="heading-how-to-position-the-cursor-for-text-manipulation"><strong>How to Position the Cursor for Text Manipulation</strong></h3>
<p>Before using any of the commands listed below (copy, cut, paste, and delete), it's important to understand where to place the cursor.</p>
<ul>
<li><p><strong>Copy (Yank), Cut, Delete:</strong> For most operations, the cursor needs to be placed <strong>at the starting point of the text</strong> you want to act upon. This means if you're copying or cutting a word, place the cursor at the <strong>beginning</strong> of the word. If you're working with a line, the cursor should be anywhere on that line. For paragraph-based operations, position the cursor anywhere within the paragraph.</p>
</li>
<li><p><strong>Paste:</strong> The text will be pasted at the cursor's <strong>current position</strong>. So, ensure your cursor is placed where you want the copied or cut content to appear.</p>
</li>
</ul>
<p>For example, Let’s say I have a file.txt that has the following content -</p>
<pre><code class="lang-bash"><span class="hljs-comment"># file.txt</span>
Hey readers,  
In this blog, we<span class="hljs-string">'re learning Vim. This file is for demonstration purposes,
where we'</span>ll explore various editing commands like cut, copy, paste, and delete. Let<span class="hljs-string">'s dive in!  

Vim is a powerful text editor that comes pre-installed on most Unix-based systems.
Mastering Vim can significantly boost your efficiency as a developer.  

To start with, let'</span>s learn some basic navigation and text manipulation commands.
Stay tuned as we <span class="hljs-built_in">break</span> down each <span class="hljs-built_in">command</span> with examples!
</code></pre>
<p>All the commands mentioned below will use this file as reference to explain examples.</p>
<h3 id="heading-1-copy-yank"><strong>1. Copy (Yank)</strong></h3>
<p>In Vim, copying is called "yanking." Use the following commands to copy text. The cursor's position is important to ensure the correct text is copied.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Command</td><td>Description</td><td>Example</td></tr>
</thead>
<tbody>
<tr>
<td><code>yl</code></td><td>Copies a letter from the current cursor position (cursor must be on the left of the letter you want to copy)</td><td>If your cursor is on the left of <strong>"H"</strong> in <code>Hey readers,</code> and you type <code>yl</code>, Vim will copy <strong>"H"</strong> (only one character).</td></tr>
<tr>
<td><code>yw</code></td><td>Copies a word (cursor must be at the beginning of the word)</td><td>If your cursor is on the left of <strong>"blog,"</strong> in the sentence, Typing <code>yw</code> will copy <strong>"blog,"</strong> (including the comma).</td></tr>
<tr>
<td><code>yy</code></td><td>Copies the entire line (cursor can be anywhere on the line)</td><td>If your cursor is at any position on line 1 <strong>Hey readers,</strong> Typing <code>yy</code> will copy the entire line</td></tr>
<tr>
<td><code>2yy</code></td><td>Copies two lines, including the current cursor line (cursor can be anywhere on the first line)</td><td>If your cursor is at any position on line 1 <strong>Hey readers,</strong> Typing <code>2yy</code> will copy the entire line along with the next line.</td></tr>
<tr>
<td><code>y{</code></td><td>Copies the rest of the paragraph above the line where the cursor currently is (and including that line)</td><td>If your cursor is anywhere inside this paragraph 2 (Vim is a powerful text editor…), Typing <code>y{</code> will copy everything from the start of this paragraph up to the cursor position.</td></tr>
<tr>
<td><code>y}</code></td><td>Copies the rest of the paragraph below the line where the cursor currently is (and including that line)</td><td>If your cursor is anywhere inside this paragraph 2 (Vim is a powerful text editor…), Typing <code>y}</code> will copy everything from the current cursor position down to the end of the paragraph.</td></tr>
<tr>
<td><code>yG</code></td><td>Copies everything from the current line to the end of the file (cursor must be at the line where you want the copy operation to start)</td><td>If your cursor is at the beginning of this line “<strong>Vim is a powerful text editor…”,</strong> typing yG will copy this line and everything below it until the end of the file</td></tr>
</tbody>
</table>
</div><h3 id="heading-2-cut-change"><strong>2. Cut (Change)</strong></h3>
<p>Cutting in Vim is known as "changing" the text. The cut operation replaces the text. Just like with copying, the cursor's position is important when using cut commands.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Command</td><td>Description</td><td>Example</td></tr>
</thead>
<tbody>
<tr>
<td><code>cl</code></td><td>Cuts a letter from the current cursor position (cursor must be on the left of the letter you want to cut)</td><td>If your cursor is on the <strong>"H"</strong> in <code>"Hey readers,"</code> and you type <code>cl</code>, Vim will delete <strong>"H"</strong> and switch to insert mode, allowing you to type a replacement.</td></tr>
<tr>
<td><code>cw</code></td><td>Cuts a word (cursor must be at the beginning of the word)</td><td>If your cursor is on the <strong>"blog,"</strong> in the sentence, typing <code>cw</code> will delete <strong>"blog,"</strong> (including the comma) and switch to insert mode, allowing you to type a replacement.</td></tr>
<tr>
<td><code>caw</code></td><td>Cuts a word along with trailing whitespace (cursor must be at the beginning of the word)</td><td>If your cursor is anywhere inside <strong>"blog,"</strong>, typing <code>caw</code> will delete <strong>" blog,"</strong> (including the preceding space) and switch to insert mode.</td></tr>
<tr>
<td><code>cc</code></td><td>Cuts the entire line (cursor can be anywhere on the line)</td><td>If your cursor is at any position on line 1 (<code>Hey readers,</code>), typing <code>cc</code> will delete the whole line and switch to insert mode.</td></tr>
<tr>
<td><code>2cc</code></td><td>Cuts two lines, including the current cursor line (cursor can be anywhere on the first line)</td><td>If your cursor is at any position on line 1 (<code>Hey readers,</code>), typing <code>2cc</code> will delete this line along with the next line and switch to insert mode.</td></tr>
<tr>
<td><code>c{</code></td><td>Cuts the text in the paragraph above the cursor’s location</td><td>If your cursor is anywhere inside paragraph 2 (<code>Vim is a powerful text editor…</code>), typing <code>c{</code> will delete everything from the cursor position to the start of the paragraph and switch to insert mode.</td></tr>
<tr>
<td><code>c}</code></td><td>Cuts the text in the paragraphs below the cursor’s location</td><td>If your cursor is anywhere inside paragraph 2 (<code>Vim is a powerful text editor…</code>), typing <code>c}</code> will delete everything from the cursor position to the end of the paragraph and switch to insert mode.</td></tr>
<tr>
<td><code>cG</code></td><td>Cuts everything from the current line to the end of the file (cursor must be at the line where you want the cut operation to start)</td><td>If your cursor is at the beginning of this line (<code>Vim is a powerful text editor…</code>), typing <code>cG</code> will delete this line and everything below it until the end of the file, then switch to insert mode.</td></tr>
</tbody>
</table>
</div><h3 id="heading-3-paste"><strong>3. Paste</strong></h3>
<p>To paste the copied or cut text, use the following commands. The pasted text will appear at the <strong>current cursor position</strong>.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Command</td><td>Description</td></tr>
</thead>
<tbody>
<tr>
<td><code>p</code> (Lowercase)</td><td>Pastes the copied or cut text <strong>after</strong> the cursor</td></tr>
<tr>
<td><code>P</code> (Uppercase)</td><td>Pastes the copied or cut text <strong>before</strong> the cursor</td></tr>
</tbody>
</table>
</div><h3 id="heading-4-delete"><strong>4. Delete</strong></h3>
<p>Deleting text in Vim allows you to remove unwanted text while remaining in command mode. The cursor must be positioned correctly to delete the intended text. Once deleted, you can still paste the deleted text to a new location.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Command</td><td>Description</td><td>Example</td></tr>
</thead>
<tbody>
<tr>
<td><code>dl</code></td><td>Deletes a letter from the current cursor position (cursor must be on the left of the letter you want to delete)</td><td>If your cursor is on <strong>"H"</strong> in <code>"Hey readers,"</code> and you type <code>dl</code>, Vim will delete <strong>"H"</strong>.</td></tr>
<tr>
<td><code>dw</code></td><td>Deletes a word (cursor must be at the beginning of the word)</td><td>If your cursor is on <strong>"blog,"</strong> in the sentence, typing <code>dw</code> will delete <strong>"blog,"</strong> (including the comma).</td></tr>
<tr>
<td><code>daw</code></td><td>Deletes a word along with trailing whitespace (cursor must be at the beginning of the word)</td><td>If your cursor is anywhere inside <strong>"blog,"</strong>, typing <code>daw</code> will delete <strong>" blog,"</strong> (including the preceding space).</td></tr>
<tr>
<td><code>dd</code></td><td>Deletes the entire line (cursor can be anywhere on the line)</td><td>If your cursor is at any position on line 1 (<code>Hey readers,</code>), typing <code>dd</code> will delete the whole line.</td></tr>
<tr>
<td><code>2dd</code></td><td>Deletes two lines, including the current cursor line (cursor can be anywhere on the first line)</td><td>If your cursor is at any position on line 1 (<code>Hey readers,</code>), typing <code>2dd</code> will delete this line along with the next line.</td></tr>
<tr>
<td><code>d{</code></td><td>Deletes the paragraph above the cursor (cursor can be anywhere in the paragraph you want to delete)</td><td>If your cursor is anywhere inside paragraph 2 (<code>Vim is a powerful text editor…</code>), typing <code>d{</code> will delete everything from the cursor position to the start of the paragraph.</td></tr>
<tr>
<td><code>d}</code></td><td>Deletes the paragraph below the cursor (cursor can be anywhere in the paragraph you want to delete)</td><td>If your cursor is anywhere inside paragraph 2 (<code>Vim is a powerful text editor…</code>), typing <code>d}</code> will delete everything from the cursor position to the end of the paragraph.</td></tr>
<tr>
<td><code>dG</code></td><td>Deletes everything from the current line to the end of the file (cursor must be at the line where you want the delete operation to start)</td><td>If your cursor is at the beginning of this line (<code>Vim is a powerful text editor…</code>), typing <code>dG</code> will delete this line and everything below it until the end of the file.</td></tr>
</tbody>
</table>
</div><h3 id="heading-5-other-useful-commands"><strong>5. Other Useful Commands</strong></h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Commands</td><td>Description</td></tr>
</thead>
<tbody>
<tr>
<td><code>gg</code></td><td>Moves the cursor to the first line of the file</td></tr>
<tr>
<td><code>G</code></td><td>Moves the cursor to the last line of the file</td></tr>
<tr>
<td><code>:se nu</code></td><td>Sets line numbers in the file</td></tr>
<tr>
<td><code>:se nonu</code></td><td>Removes line numbers from the file</td></tr>
<tr>
<td><code>:u</code></td><td>Undoes the last action</td></tr>
<tr>
<td><code>:10</code></td><td>Jumps to line 10 (for example)</td></tr>
</tbody>
</table>
</div><p>Note that <strong>Delete</strong> removes text but doesn’t store it in the system clipboard by default. The text goes into Vim’s unnamed register, meaning it can be pasted within Vim but not outside it. <strong>Cut</strong> explicitly stores text in the clipboard so you can paste it outside Vim as well.</p>
<h2 id="heading-search-and-replace-commands"><strong>Search and Replace Commands</strong></h2>
<p>Vim provides powerful search and replace functionality that allows you to find specific words or patterns and replace them efficiently. Understanding how to search and replace text is key to improving your productivity when editing large files.</p>
<p>Below is a breakdown of the various search and replace commands in Vim.</p>
<h3 id="heading-search-commands"><strong>Search Commands</strong></h3>
<ul>
<li><p><strong>Search Forward</strong> (<code>/</code>): When you want to search for a word or pattern below the cursor, use the <code>/</code> command. This will search forward in the file.</p>
</li>
<li><p><strong>Search Backward</strong> (<code>?</code>): Similarly, if you want to search for a word or pattern above the cursor, use the <code>?</code> command. This will search backward in the file.</p>
</li>
</ul>
<p>After performing a search, you can navigate through the search results:</p>
<ul>
<li><p><code>n</code>: Go to the next match in the same direction (forward if <code>/</code>, backward if <code>?</code>).</p>
</li>
<li><p><code>N</code>: Go to the previous match in the opposite direction (backward if <code>/</code>, forward if <code>?</code>).</p>
</li>
</ul>
<h3 id="heading-replace-commands"><strong>Replace Commands</strong></h3>
<p>Once you've located the word or pattern you want to replace, Vim provides several commands for replacing text.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Command (In command mode)</td><td>Description</td></tr>
</thead>
<tbody>
<tr>
<td><code>/search_word</code></td><td>Searches for the given word and moves the cursor to its first occurrence below the current cursor position.</td></tr>
<tr>
<td><code>:s/search_word/replace_word</code></td><td>Replaces the first occurrence of <code>search_word</code> with <code>replace_word</code> in the current line.</td></tr>
<tr>
<td><code>:s/search_word/replace_word/g</code></td><td>Replaces all occurrences of <code>search_word</code> with <code>replace_word</code> in the current line.</td></tr>
<tr>
<td><code>:%s/search_word/replace_word</code></td><td>Replaces the first occurrence of <code>search_word</code> with <code>replace_word</code> in the entire file.</td></tr>
<tr>
<td><code>:%s/search_word/replace_word/g</code></td><td>Replaces all occurrences of <code>search_word</code> with <code>replace_word</code> in the entire file.</td></tr>
</tbody>
</table>
</div><p>Here’s an example:</p>
<p>In Vim, the <code>/Tanishka</code> pattern searches for an exact, case-sensitive match of the word "Tanishka."</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736795075490/1ecbc6f4-65ef-46a9-841d-dbb3251f8ec7.png" alt="1ecbc6f4-65ef-46a9-841d-dbb3251f8ec7" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>To replace "Tanishka" with another word, like "Linux," you can use the substitution command like this: <code>:s/Tanishka/Linux</code>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736794982127/c9dffdba-c250-4601-8e3b-950d875908f8.png" alt="c9dffdba-c250-4601-8e3b-950d875908f8" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>By default, this command replaces only the first occurrence of "Tanishka" in the line where the cursor is located.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736794997175/9831423d-4aab-43a1-9b06-408fb5dd4828.png" alt="9831423d-4aab-43a1-9b06-408fb5dd4828" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>If you want to replace all occurrences of "Tanishka" in the same line, you need to add the <code>g</code> (global) flag after the replacement string like this: <code>:s/Tanishka/Linux/g</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736795009744/c53d0de2-b7b4-4154-baaa-3d28fb3c29db.png" alt="c53d0de2-b7b4-4154-baaa-3d28fb3c29db" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>This ensures that every instance of "Tanishka" in the current line is replaced with "Linux."</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736795017539/dac817e5-8130-44d4-8f09-d887e42cc859.png" alt="dac817e5-8130-44d4-8f09-d887e42cc859" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Similarly, the <code>%</code> symbol is used to specify the <strong>entire file</strong> when performing a substitution. Here's how it works in combination with the substitution command:</p>
<ol>
<li><p><strong>Replace the first occurrence in each line of the file:</strong></p>
<ul>
<li><code>:%s/Tanishka/Linux</code>: This command replaces only the first occurrence of "Tanishka" in each line of the file.</li>
</ul>
</li>
<li><p><strong>Replace all occurrences in the entire file:</strong></p>
<ul>
<li><code>:%s/Tanishka/Linux/g</code>: The addition of the <code>g</code> (global) flag ensures that all occurrences of "Tanishka" in every line of the file are replaced with "Linux."</li>
</ul>
</li>
</ol>
<h2 id="heading-how-to-read-files-using-more-and-less"><strong>How to Read Files using</strong> <code>more</code> <strong>and</strong> <code>less</code></h2>
<h3 id="heading-the-cat-command">The <code>cat</code> command</h3>
<p>The cat command is often used to read file content.</p>
<p>For example:</p>
<pre><code class="lang-bash">cat file.txt <span class="hljs-comment"># Displays content of file</span>
</code></pre>
<p>While the <code>cat</code> command is a straightforward tool for viewing file contents, its simplicity often falls short when working with large files or when precise navigation is required. That’s where the <code>more</code> and <code>less</code> commands come into play, offering enhanced functionality for viewing and navigating text efficiently.</p>
<h3 id="heading-the-more-command">The <code>more</code> Command</h3>
<p>The <code>more</code> command allows you to view files one screen at a time, making it a significant upgrade from <code>cat</code> when dealing with large files. But it comes with limitations in terms of backward navigation and advanced features.</p>
<p>Here’s the syntax for <code>more</code>:</p>
<pre><code class="lang-bash">more [FILENAME]
</code></pre>
<p>And here’s an example:</p>
<pre><code class="lang-bash">more file.txt <span class="hljs-comment"># Displays content of file.txt one page at a time</span>
</code></pre>
<p>Keys used while viewing:</p>
<ol>
<li><p>Spacebar: Moves forward by one page</p>
</li>
<li><p>Enter: Moves forward by one line</p>
</li>
<li><p>b: Moves back by one page</p>
</li>
<li><p>q: Quit and exit file content</p>
</li>
</ol>
<h3 id="heading-the-less-command">The <code>less</code> Command</h3>
<p>The <code>less</code> command is often considered a superior alternative to <code>more</code> due to its advanced navigation capabilities and flexibility. Unlike <code>more</code>, <code>less</code> allows both forward and backward navigation, making it ideal for reviewing large files or logs.</p>
<p>Here’s its syntax:</p>
<pre><code class="lang-bash">less [FILENAME]
</code></pre>
<p>And here’s an example:</p>
<pre><code class="lang-bash">less file.txt <span class="hljs-comment"># Displays content of file.txt one page at a time</span>
</code></pre>
<p>Keys used while viewing:</p>
<ol>
<li><p>Spacebar: Moves forward by one page</p>
</li>
<li><p>Enter: Moves forward by one line</p>
</li>
<li><p>b: Moves back by one page</p>
</li>
<li><p>Up/Down arrow key: Moves up or down by one line</p>
</li>
<li><p>q: Quit and exit less</p>
</li>
</ol>
<p>The only major difference between the <code>more</code> and <code>less</code> commands is that the less command allows bidirectional navigation, so it’s typically more convenient to use.</p>
<h2 id="heading-text-filters"><strong>Text Filters</strong></h2>
<p>A <strong>text filter</strong> in Linux is a command-line utility that processes text data by modifying, extracting, or formatting it before outputting the result.</p>
<h3 id="heading-horizontal-filters">Horizontal filters</h3>
<p>Horizontal filtering focuses on extracting, manipulating, or displaying specific lines of a file or command output. Common tools include <code>head</code>, <code>tail</code>, and <code>grep</code>.</p>
<ol>
<li><p><code>head</code>: The head command displays the first few lines of a file. By default, it shows the first 10 lines. Here’s its syntax:</p>
<pre><code class="lang-bash"> head [OPTIONS] [FILENAME]
</code></pre>
<p> And here’s an example of how to use it:</p>
<pre><code class="lang-bash"> head file.txt <span class="hljs-comment"># Displays first ten lines from file.txt</span>
 head -n 5 file.txt <span class="hljs-comment"># Displays first five lines from file.txt</span>
</code></pre>
</li>
<li><p><code>tail</code>: The tail command displays the last few lines of a file. By default, it shows the last 10 lines. Here’s its syntax:</p>
<pre><code class="lang-bash"> tail [OPTIONS] [FILENAME]
</code></pre>
<p> And here’s an example:</p>
<pre><code class="lang-bash"> tail file.txt <span class="hljs-comment"># Displays last ten lines from file.txt</span>
 tail -n 5 file.txt <span class="hljs-comment"># Displays last five lines from file.txt</span>
</code></pre>
</li>
<li><p><code>grep</code>: The grep command searches for patterns within a file or input. It filters out lines that match a given pattern. Here’s its syntax:</p>
<pre><code class="lang-bash"> grep [OPTIONS] [PATTERN] [FILENAME]
</code></pre>
<p> Options:</p>
<ul>
<li><p><code>-i</code>: Case-insensitive search.</p>
</li>
<li><p><code>-v</code>: Invert the match (exclude matching lines).</p>
</li>
<li><p><code>-n</code>: Show line numbers of matches.</p>
</li>
</ul>
</li>
</ol>
<p>    Example</p>
<pre><code class="lang-bash">    grep Tanishka data.txt <span class="hljs-comment"># Displays lines that have 'Tanishka' in them</span>
    grep -i Tanishka data.txt <span class="hljs-comment"># Displays lines that have 'Tanishka' irrespective of case</span>
    grep -v Tanishka data.txt <span class="hljs-comment"># Displays lines that do not have 'Tanishka' in them</span>
    grep -n Tanishka data.txt <span class="hljs-comment"># Displays lines that have 'Tanishka' in them along with number line</span>
</code></pre>
<h3 id="heading-vertical-filters">Vertical Filters</h3>
<ol>
<li><p><code>cut</code>: The cut command displays selected parts of lines from each file based on delimiters, byte positions, or character fields. Here’s its syntax:</p>
<pre><code class="lang-bash"> cut [OPTION] [FILENAME]
</code></pre>
<p> It also comes with various options:</p>
<ul>
<li><p><code>-c</code>: Extract specific characters.</p>
</li>
<li><p><code>-b</code>: Extract specific bytes.</p>
</li>
<li><p><code>-d</code>: Specify a custom delimiter (default is tab).</p>
<ul>
<li><code>cut -d ":" -f 2 file.txt</code> → Second field separated by <code>:</code>.</li>
</ul>
</li>
<li><p><code>-f</code>: Extract specific fields.</p>
<ul>
<li><code>cut -d "," -f 1,3 file.csv</code> → Fields 1 and 3 from a CSV.</li>
</ul>
</li>
</ul>
</li>
</ol>
<p>    Example:</p>
<pre><code class="lang-bash">    cut -c 1-10 Sample.txt <span class="hljs-comment"># Displays characters from position 1 to 10</span>
    cut -c 5 Sample.txt <span class="hljs-comment"># Displays character at position 5</span>
    cut -c 3,5 Sample.txt <span class="hljs-comment"># Displays characters from position 3 and 5 only</span>
    cut -d <span class="hljs-string">" "</span> -f 1 Sample.txt <span class="hljs-comment"># Displays first field separated by a space</span>
    cut -d <span class="hljs-string">" "</span> -f 2 Sample.txt <span class="hljs-comment"># Displays second field separated by a space</span>
    cut -d <span class="hljs-string">" "</span> -f 3 Sample.txt <span class="hljs-comment"># Displays third field separated by a space</span>
    cut -d <span class="hljs-string">" "</span> -f 1-3 Sample.txt <span class="hljs-comment"># Displays first to third fields separated by a space</span>
    cut -d <span class="hljs-string">" "</span> -f 1,3 Sample.txt <span class="hljs-comment"># Displays first and third fields separated by a space</span>
    cut -d <span class="hljs-string">":"</span> -f 5 /etc/passwd <span class="hljs-comment"># Displays fifth field separated by : in /etc/passwd</span>
</code></pre>
<h2 id="heading-text-summarization-tool-wc"><strong>Text Summarization Tool:</strong> <code>wc</code></h2>
<p>The <code>wc</code> (word count) command is used to display the number of lines, words, characters, or bytes in a file or input. It is a simple yet powerful utility you can use to summarize text content.</p>
<p>Here’s its syntax:</p>
<pre><code class="lang-bash">wc [OPTION] [FILENAME]
</code></pre>
<p>And here are its options:</p>
<ul>
<li><p><code>-l</code>: Displays the number of lines.</p>
</li>
<li><p><code>-w</code>: Displays the number of words.</p>
</li>
<li><p><code>-c</code>: Displays the number of bytes.</p>
</li>
<li><p><code>-m</code>: Displays the number of characters (useful for multibyte characters).</p>
</li>
<li><p><code>-L</code>: Displays the length of the longest line.</p>
</li>
</ul>
<p>Example:</p>
<pre><code class="lang-bash">wc Sample.txt <span class="hljs-comment"># Displays line count, word count, and byte count in Sample.txt</span>
wc -w Sample.txt <span class="hljs-comment"># Displays number of words in Sample.txt</span>
wc -l Sample.txt <span class="hljs-comment"># Displays number of lines in Sample.txt</span>
wc -L Sample.txt <span class="hljs-comment"># Displays number of characters in longest line in Sample.txt</span>

wc -c Sample.txt <span class="hljs-comment"># Displays number of bytes in Sample.txt (Actual storage size)</span>
wc -m Sample.txt <span class="hljs-comment"># Displays number of characters in Sample.txt (Actual number of characters regardless of enoing)</span>

<span class="hljs-comment"># ABCD😄</span>
wc -c above.txt <span class="hljs-comment"># "ABCD" = 4 bytes + "😄" = 4 bytes. 4 + 4 = 8 bytes</span>
wc -m above.txt <span class="hljs-comment"># "ABC" = 4 + "😄" = 1 byte. 4 + 1 = 5 bytes</span>
</code></pre>
<h2 id="heading-final-words">Final Words</h2>
<p>In this article, we covered the basics of using Vim, a powerful and flexible text editor. We started with how to open a file in Vim and then you learned about its modes. You also learned how to navigate through files, edit text, and use features like search and replace to save time. We also explored a helpful summarization tool.</p>
<p>If you're new to Linux and want to build a strong foundation, check <a target="_blank" href="https://www.freecodecamp.org/news/guide-to-rhel-linux-basics/">my previous article</a> where I cover the basics of Linux, including essential commands and tips for beginners. It’s a perfect starting point to complement what you’ve learned about Vim here!</p>
<p>Keep practising these commands, and soon they'll become second nature to you. Mastery comes with repetition, so continue experimenting and applying these fundamentals in real-world scenarios.</p>
<p>Stay tuned for more articles. Get ready to take your RHEL skills to the next level.</p>
<p><a target="_blank" href="https://linktr.ee/tanishkamakode">Let’s connect!</a></p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
