Kushal Das

FOSS and life. Kushal Das talks here.


Securing via systemd, a story

Last night I deployed a https://writefreely.org based blog and secured it with systemd by adding DynamicUser=yes. But the service itself could not write to the sqlite database.

Feb 28 21:37:52 kushaldas.se writefreely[1652088]: ERROR: 2024/02/28 21:37:52 database.go:3000: Couldn't insert into posts: attempt to write a readonly database

Today morning I realized that the settings blocked writing to all paths except few temporary ones. I had to add a StateDirectory and used the same in WorkingDirectory so that the service works correctly.

Startup/execution time for a specific command line tool

Generally I don’t have to bother about the startup time of any command line tool. For a human eye and normal day to day usage, if a command takes half a second to finish the job, it is not much of a problem. But, the story changes the moment we talk about a command which we have to run multiple times. What about about a command which have to execute multiple times every minute? This is the time when the startup and execution time matters.

A few weeks ago I was looking at a Python script which was executing as part of Nagios run, it was doing an API call to a remote server with JSON data coming in as command line arguments. Now, to make it scale more the first thought was to move the actual API call to a different process and get the original script to load things into a Redis queue. But, the other issue was the startup time for the Python script, having something with lesser startup time would be more help in this case, where nagios may execute the script/command over a few hundred times in every minute.

So, first I rewrote the code in Rust, and that made things multiple times faster. But, just for fun I wanted to see if writing it in golang will help or not. And I am kind of surprised to see the startup/execution time difference. I am using hyperfine for benchmark.

Python script

  Time (mean ± σ):     195.5 ms ±  11.3 ms    [User: 173.6 ms, System: 19.7 ms]
  Range (min … max):   184.5 ms … 228.8 ms    12 runs

Rust code

  Time (mean ± σ):      31.1 ms ±   8.4 ms    [User: 27.3 ms, System: 3.0 ms]
  Range (min … max):    24.6 ms …  79.0 ms    37 runs

View the code.

Golang code

  Time (mean ± σ):       3.2 ms ±   1.6 ms    [User: 1.0 ms, System: 1.7 ms]
  Range (min … max):     2.6 ms …  19.6 ms    140 runs

The code.

For now, we will go with the golang based code to do the work. But, if someone can explain the different ways Rust/Golang code starts up, that would be helpful to learn why such a speed difference.

Oh, here is the result of a quick poll on Fediverse about startup time.

40% people cares about startup time in cli


I received a PR for the Rust code from, which removes the async POST requests, and made the whole thing even simpler and faster (30x).

  Time (mean ± σ):       1.0 ms ±   0.6 ms    [User: 0.5 ms, System: 0.3 ms]
  Range (min … max):     0.5 ms …   4.3 ms    489 runs

Setting up a personal DoH server

DoH is a hot discussion point in both the privacy and DNS people. There are many criticisms, including encryption support of the clients or still trusting a third party. There is an excellent talk from Bert Hubert on this topic.

In this post, we will learn how to set up our own personal DoH server. I am not posting any tips on the IPTABLES rules, you should be able to add those based on what all services you run on the server.

We will use unbound as the recursive DNS server in our setup. In the server, we can easily install it via the OS package management (apt/dnf).

Getting the root name servers' details

We should get a fresh copy of the root name servers' details, and then you can have a cron job every six months to get a fresh copy.

curl --output /etc/unbound/root.hints https://www.internic.net/domain/named.cache

Setting up unbound

I am using the following configuration

# Unbound configuration file for Debian.
# See the unbound.conf(5) man page.
# See /usr/share/doc/unbound/examples/unbound.conf for a commented
# reference config file.
# The following line includes additional configuration files from the
# /etc/unbound/unbound.conf.d directory.
include: "/etc/unbound/unbound.conf.d/*.conf"
        root-hints: "/etc/unbound/root.hints"
        access-control: allow
        use-syslog: yes

Then you can get the configuration checked via the unbound-checkconf command.

# unbound-checkconf
unbound-checkconf: no errors in /etc/unbound/unbound.conf
systemctl enable unbound
systemctl start unbound  

Setup Nginx along with certbot

Setup Nginx and use the certbot tool to get the SSL certificate. Here is the configuration I am using, the main point to notice the upstream section.

upstream dns-backend {
    keepalive 30;
server {
    listen 80;
    listen [::]:80;

    location /.well-known/acme-challenge {
        alias /var/www/leftover;
    root /var/www/html;

    server_name yourdomain;
    return 301 https://$host;

server {
    listen 443 ssl http2;

    # if you wish, you can use the below line for listen instead
    # which enables HTTP/2
    # requires nginx version >= 1.9.5
    # listen 443 ssl http2;

    server_name yourdomain;
    index index.html;

    ssl_certificate /etc/letsencrypt/live/yourdomain/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/yourdomain/privkey.pem;

    # Turn on OCSP stapling as recommended at
    # https://community.letsencrypt.org/t/integration-guide/13123
    # requires nginx version >= 1.3.7
    ssl_stapling on;
    ssl_stapling_verify on;

    # modern configuration. tweak to your needs.
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;

    # Uncomment this line only after testing in browsers,
    # as it commits you to continuing to serve your site over HTTPS
    # in future
    # add_header Strict-Transport-Security "max-age=31536000";

    rewrite ^(.*).php https://www.youtube.com/watch?v=dQw4w9WgXcQ last;
    # maintain the .well-known directory alias for renewals
    location /.well-known {

        alias /var/www/yourdomain/.well-known;

        location / {
            root   /var/www/html;
            index  index.html;
    location /dns-query {
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_set_header X-NginX-Proxy true;
        proxy_set_header Connection "";
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_redirect off;
        proxy_set_header        X-Forwarded-Proto $scheme;
        proxy_read_timeout 86400;
        proxy_pass http://dns-backend/dns-query;


Then start the Nginx server.

Setting up the DoH server

You will have to setup golang as we will have to compile the project. First step is always the git clone :)

git clone https://github.com/m13253/dns-over-https.git
cd dns-over-https
make install 

As next step we will modify the configuration file of the dns-over-https tool at /etc/dns-over-https/doh-server.conf. Now, there are many different configuration options available, I am using only a small part of it. Check the github repo for an uptodate commented configuration example.

listen = [
local_addr = ""
cert = ""
key = ""
path = "/dns-query"
upstream = [
timeout = 10
tries = 3
verbose = false
log_guessed_client_ip = false

We are asking the tool to talk to the unbound running on the same server. Next, we can start and enable the service. Remember to check the logs for any errors due to typos.

systemctl restart doh-server
systemctl enable doh-server

Testing the setup

You can test the setup by making a call using curl command and using Python's json module to give you a readable output.

curl -s "https://yourdomain/dns-query?name=dgplug.org.org&type=A" | python3 -m json.tool

You can now use the server in the General -> Network Settings section of Firefox.

setting up firefox

A new tool to render my blog


Back in 2013, I wrote Shonku in golang. It helped me in two important things:

  • Having a static blogging tool which works (worked for the last 5 years).
  • To help me to learn basics on golang.

Now, I think it worked for me. I could focus on writing the actual content of the posts than anything else. The tool has a few flaws, but, none of them had any issue with my blogging requirements. It just worked for me. I could have written it in Python (in much less time), but, learning a new language is always fun.

new new

As I am trying to write more and more Rust, I decided to write a new tool in Rust and use that for my blog https://kushaldas.in.

This is very initial code, and you can easily figure out that I still don’t know how to write more idiomatic Rust yet. However, this works. The last couple of the posts were made using this tool, and I also regenerated the whole site in between.

The cargo build --release command takes time, at the same time the release binary is insanely fast.