Please enable JavaScript to view the comments powered by Disqus.

Teddy Hartanto

thoughts and experiments

I hiked Mt Batur in Bali a few days ago and I heard an eye-opening anecdote. When my hiking guide — Kadek Bagawata — showed up, I was quite surprised. He was topless, barefoot, and wore only a sarung.

Sarung

I thought he was going to change into a proper hiking outfit. He did not...

He was very wholesome by the way. Before we embarked on the hike, he expressed gratitude that we're all able to come together and celebrate this hike together. He encouraged us to sing along with him on the journey. Yeah... he carried a guitar and a drum for the hike. That is insane. I've only seen that in video games, not in real life!

Because of his bare outfit, I thought, “oh, the terrain must be easy.” Well, it wasn't. There were screes. So many loose rocks and pebbles. He walked on them like it's nothing!

At the summit, we were greeted by a blanket of dense mist instead of a rewarding sunrise. At the top, I asked him why he decided to hike barefoot. Apparently, he had a good reason. He developed Tourette's Syndrome when he was 11. By chance, he discovered that his condition was hugely alleviated when he hike up the mountain barefoot.

That's when it hit me. Well, that could make sense. Tourette's is a nervous condition. As far as I understand, reflexology works by stimulating the nerves.

Walking on stones

As always, that made me meditate on how much we're taking for granted today are actually good for us? I wore shoes from when I was a little kid and it became something normal. But, is it really normal for humans to wear heavily padded footwear? What's the history of shoes? I know there's a barefoot running movement. I haven't quite wrapped my head around that topic, but I shall explore that.

Brb, going to walk on some stones downstairs...


A snail on the sidewalk

A snail on the sidewalk

You're walking or running on the sidewalk.

You spotted a snail.

There, in the middle of the sidewalk.

Do you avoid and walk past it?

Or do you pick it up and move it aside so no one would accidentally run over it?

A plate of stir-fry long beans and a fork

A plate of stir-fry long beans and a fork

You're in a restaurant, eating with friends or family.

The waiter served a plate of long beans right in front of you.

There's a fork provided with it.

Do you use the fork to try grabbing as much long beans as you want?

Or do you ask the waiter if they have like a big spoon or something that will make it easier to scoop the long beans?

Company with broken processes

You just started a new job in a new company.

It's been 6 months.

You start noticing the ugly things — the gaps in processes, the toxic behavior of a teammate that goes unnoticed, the mess in the systems.

Do you turn a blind eye, avoid the ugly things, and focus on what you like?

Or do you advocate for fixing the broken processes, call out the toxic behavior, fix the systems?


I've noticed two types of response when dealing with an external situation:

  • influence; or
  • adapt

Sometimes, when things are out of our control, we have no choice but to adapt. We change our mindset and perspective.

Some other times, we are given a choice: we can influence, or we can adapt. Influencing is more difficult. You have to take actions. It's more active. Adapting is relatively easier. It's passive. I'm not saying one is better than the other. I'm saying, be intentional with your choice of response.


I had a fascinating experience today. I was the de-facto event photographer at Ruth's casual wedding party. For the longest time, I've been mainly taking pics of mostly still subjects (natural landscape, buildings, etc) in an outdoor setting where light is typically abundant. Today, I had to take pictures of people in a completely different setting (moving subjects, indoor, with varying degree of lights). I have to admit that I was inexperienced. But, I learnt a few things. It was a great opportunity to practice my photography.


Lesson 1: on mind game

I'm an introvert. I don't mind approaching individual strangers. But, approaching a group of strangers talking amongst themselves? That was uncomfortable for me. It was out of my comfort zone. I was a bit disappointed that I wasn't proactive enough to invite people to take group pics. Of course, it doesn't come naturally to me. But, I could've done better. In retrospect, I feel that if I had change my mindset, I would have been able to overcome that mental blocker. If only I keep in my mind: “today's an important event for Ruth, and I have one job — to capture and memorialize the space, time, and people at today's event.” If I can keep that in mind, it would've been easy to overcome my reluctance to approach groups of people. I would have been able to capture more attendees.


Lesson 2: on candid pics

Instead, I took pictures of people discreetly, thinking I was going to capture some candid pics. But, candid pics are tricky, aren't they? In this particular event, there were a few obstacles to taking good candid pics: 1. We had a buffett. People don't clear their empty plates after they eat. Empty plates don't look great on pictures. People eating also don't look great on pictures. Those are unglamorous. I would've been pissed if I saw a photographer took a picture of me eating. Yet, that's the exact mistake. 2. People sat in a circle, making it difficult to include everyone's faces in the pictures. It's not nice to take pictures of some faces and some backs. 3. I couldn't easily position myself discreetly in a good angle to take good candid pics. The space was quite packed, and if I move to a certain good angle, people will notice. Then, it's won't be candid anymore.

In hindsight, it would've been nicer to just invite people to take group pics so they can prepare themselves and appear glamorous.


Lesson 3: on lenses

I'm using Sony alpha a6000 mirrorless camera. I brought 2 lenses today. One is a 35mm f/1.8 prime lens. The other is a 16-50mm f/3.5-5.6 lens. The reason I brought prime lens is because I know it take crisp pictures. It has a shallow depth of field. I thought it might be great for an event. And, I would argue that: it is, for certain setting. The reason I brought the other lens was due to the fact that the fixed focal length of the prime lens could pose a challenge when taking group pics. The field of view is not large, so it might be difficult to fit everyone into a pic.

During the event itself, the 16-50mm lens prove to be a better choice. Prime lens are too static. To take good pics, one have to move around. And there isn't much luxury to move around. A photographer has to be quick. Moments are fleeting. A prime lens also has a shallow depth of field. That becomes a problem when people stand in front of one another. Some people will get the focus, while others will be blurred. Good composition is difficult to achieve because it highly depends on the photographer's positioning, since zooming in and out is not an option. Lastly, as mentioned, the fixed focal length makes it hard to fit many people in a picture.

Telephoto lens is probably the best because its long-ranging zoom helps with sub-ideal positioning, rather than having to move here and there.


Lesson 4: on camera settings

Shutter speed has to be fast. People moves. Slow shutter speed creates blurry pictures when there's motion. Fast shutter speed freezes actions.

One strange thing I noticed was the different aperture on the pictures I took. That's weird because I set my camera on aperture priority mode. I intentionally set my aperture low so that I can get higher shutter speed. Should I have used shutter speed priority mode though? To answer that, I need to do some experiments.

Definitely, 1/80 shutter speed isn't enough. A quick googling tells me that 1/100 to 1/200 should be fine.


I was quite disappointed with the pics I took. Nonetheless, it was a good learning experience. I've tried to learn photography again and again in the past. But, the concepts just don't stick as much as when I practise it. As always, hands-on experience beats theory classes. Event photography is a totally different ballgame than landscape photography. I now have a higher appreciation for event photographers. They have to be super attuned to the environment they operate in. They have to think about their positions, their subjects' positions, the lighting, the turn of events, etc. They have to constantly adjust to capture the best moments so that the event owners can cherish those important and memorable events forever.


A short but intense focus leads to a better and quicker outcome than long but mild focus.

It's common sense, but I had a deeper understanding and appreciation when I delibrately reflect on it and experience it. Friends in my circle who are smart and quick to get things done are also the most focused people I know. They may not necessarily know a concept or a new technology, but they learn quickly with their intense focus.

Cultivate intense focus because it allows you to get more things done in a shorter time. You'll do more and have a fuller life. You don't need to live a long life, you just need to live a focused life.

I was prompted to write this short piece when reading an excellent write-up on procrastination by Paul Graham here. Read it!


A derivation work of https://theo-andreou.org/.

Today, I had to migrate my blog from hosting it in AWS to DigitalOcean. Before I began hosting my blog, I already had a t2.micro EC2 instance running as part of my experimentation with Linux. I thought it was convenient to host my blog in that same server. Well, it was... until the bill came. Apparently, the instance is only free for the first 12 months. After that, AWS charges me for a whopping $18/mo. Freak. I decided to switch to DO to reduce the cost to $6/mo.

It took me a couple hours to re-setup everything. So, I'm writing this in case I ever need to do the same. Perhaps this would save me some time.

Warning: I have NOT tested everything below. I merely picked commands from my history and cross-reference them to the link I gave above

# Set up OS

# Set up WriteFreely
# Install nginx & certbot
$ sudo apt install -y nginx certbot python3-certbot-nginx

# Install mysql
$ wget https://dev.mysql.com/get/mysql-apt-config_0.8.22-1_all.deb
$ sudo dpkg -i mysql-apt-config*
$ sudo apt install mysql-community-server mysql-server

# Create a writefreely system user (good practice)
$ sudo useradd -r -m -d /srv/writefreely writefreely

# Install WriteFreely
$ cd /srv/writefreely
$ wget https://github.com/writefreely/writefreely/releases/download/v0.13.1/writefreely_0.13.1_linux_amd64.tar.gz
$ sudo -u writefreely tar xfvz writefreely_0.13.1_linux_amd64.tar.gz
$ sudo -u writefreely mv writefreely teddyh.dev

# Prepare DB
$ sudo mysql
mysql> CREATE DATABASE writefreely CHARACTER SET latin1 COLLATE latin1_swedish_ci;
mysql> CREATE USER 'writefreely'@'localhost' IDENTIFIED BY 'password';
mysql> GRANT ALL PRIVILEGES ON writefreely.* TO 'writefreely'@'localhost'; 

# Copy over config

# Copy over dump file
ubuntu@old-node~$ sudo mysqldump writefreely > dump.sql
ubuntu@old-node~$ scp dump.sql teddy@new-node
teddy@new-node~$ mysql -u writefreely -p writefreely < dump.sql

# Copy / create a WriteFreely service (same as theo andreo's)
$ systemctl daemon-reload 
$ systemctl enable --now writefreely.service
$ curl localhost:8080  # test

# Copy / create nginx config (same as theo andreo's)
# Replace example.org -> teddyh.dev
$ cd /etc/nginx/sites-enabled/
$ sudo  ln -s ../sites-available/example.org
$ sudo nginx -t && sudo systemctl reload nginx

# Set up certbot
$ sudo certbot --nginx

I run WriteFreely as a systemd service. To run it in standalone mode (binding directly to port 80 & 443), I had to add a certain variable in its unit file (configuration file):

[Service]
AmbientCapabilities=CAP_NET_BIND_SERVICE

In Problems When Setting Up WriteFreely Part 2: Firewall, I explained how I couldn't access my blog over https because of the firewall I've set up ages ago and forgotten.

I showed the outputs of two curls:

╭─teddy@teddy-ubuntu ~ 
╰─$ curl -vvv http://teddyh.dev
*   Trying 54.151.219.0:80...
* TCP_NODELAY set
* connect to 54.151.219.0 port 80 failed: Connection refused
* Failed to connect to teddyh.dev port 80: Connection refused
* Closing connection 0
curl: (7) Failed to connect to teddyh.dev port 80: Connection refused
╭─teddy@teddy-ubuntu ~ 
╰─$ curl -vvv https://teddyh.dev
*   Trying 54.151.219.0:443...
* TCP_NODELAY set
* connect to 54.151.219.0 port 443 failed: Connection timed out
* Failed to connect to teddyh.dev port 443: Connection timed out
* Closing connection 0
curl: (28) Failed to connect to teddyh.dev port 443: Connection timed out

I failed to notice this at the time, but there is a subtle difference between the two outputs: Connection refused vs Connection timed out.

What do they mean?

It is easier to understand if we take a peek at the TCP layer when the curl requests are being sent:

Connection refused

─teddy@teddy-ubuntu ~ 
╰─$ sudo tcpdump -nn "host teddyh.dev and port 80"  
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on enp7s0, link-type EN10MB (Ethernet), capture size 262144 bytes
10:33:25.540898 IP 192.168.10.117.43884 > 54.151.219.0.80: Flags [S], seq 2847222361, win 64240, options [mss 1460,sackOK,TS val 564702358 ecr 0,nop,wscale 7], length 0
10:33:25.543802 IP 54.151.219.0.80 > 192.168.10.117.43884: Flags [R.], seq 0, ack 2847222362, win 0, length 0

Connection timed out

(Induced by firewall DROP rule)

╭─teddy@teddy-ubuntu ~ 
╰─$ sudo tcpdump -nn "host teddyh.dev and port 443"                                                                                                                                                          130 ↵
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on enp7s0, link-type EN10MB (Ethernet), capture size 262144 bytes
10:34:34.541405 IP 192.168.10.117.44816 > 54.151.219.0.443: Flags [S], seq 1786361284, win 64240, options [mss 1460,sackOK,TS val 564771358 ecr 0,nop,wscale 7], length 0
10:34:35.569866 IP 192.168.10.117.44816 > 54.151.219.0.443: Flags [S], seq 1786361284, win 64240, options [mss 1460,sackOK,TS val 564772387 ecr 0,nop,wscale 7], length 0
10:34:37.589852 IP 192.168.10.117.44816 > 54.151.219.0.443: Flags [S], seq 1786361284, win 64240, options [mss 1460,sackOK,TS val 564774407 ecr 0,nop,wscale 7], length 0
^C

*Note: Client IP address is 192.168.10.117, Server IP address is 54.151.219.0

Interpretation

Connection refused

The client tried to open a connection (SYN), but the server closed the connection (RST). Put simply, you (the client) knocked on your friend Bob's front door and his mom (the server) told you “Bob's not home!”

How could this happen?

  1. Bob's mom was lying. Bob was grounded, so his mom didn't want you to meet Bob. This is analogous to a firewall REJECT rule
  2. Bob's mom was telling the truth. Bob indeed wasn't at home. This is analogous to no service/process listening to the destined port on the host.

Connection timed out

Connection timed out means the client kept trying to open a connection to the destined port on the host, but there was no reply — so the client gave up trying. The client tried to open a connection (SYN) again and again, with no reply from the server. Put simply, you (the client) kept knocking on your friend Bob's front door and there was no answer.

How could this happen?

  1. Bob's at home but didn't hear you knocking. The sound of your knocks didn't reach him. This is analogous (well, not really) to a network congestion
  2. Bob's whole family kept quiet and didn't answer the door. They hear you, but they choose not to answer you. This is analogous to a firewall DROP rule
  3. Bob's whole family is not at home. This is analogous to a powered-off host. (I've confirmed this by trying to open a TCP connection to a powered-off host.) Contrast this with the Connection refused scenario in which we received a response from the server — indicating that the host is up.

Commenting seems to be a feature lacking in WriteFreely. As a blog author, I want to know your opinions on my posts. Do you agree with what I wrote? Do you know something I don't? Was I wrong in certain things?

Disqus came to mind because I've seen it on many sites. The installation is supposedly hassle-free:

  1. Sign up
  2. Create a site on Disqus
  3. Embed the JavaScript code
  4. Voila!

I added the JS snippet into the “Post Signature.” Any contents in that field should be rendered in each post.

Well... apparently, any contents but <script></script> 🤦‍♂️

Sigh. That is quite limiting. It was an intentional decision in the name of security. I think the rationale was reasonable. But I'm not sure I agree with the solution. The argument is invalid when WriteFreely is in single-user mode.

I see a couple of ways to achieve my goal:

  1. Request for Custom Javascript to be enabled on single-user mode
  2. Modify the source, build, and deploy a. to disable sanitization on “Post Signature” b. to include the custom Javascript
  3. Find loopholes to inject custom Javascript

Option 2 is slow. Option 1 is viable but would need some time and effort. Meanwhile, option 3 has been discovered. That came in handy 🙂. Ignore for a second that the solution is hacky. I argue that it is actually quite ingenious. Plus, it's the quickest way to achieve my goal. Kudos to infuerno for discovering that loophole.

That solution works well for me particularly because I only display post titles on my blog's index page. By default, Disqus' comment threads are identified by the page URL in which they are loaded. Since I force readers to visit the post page, each post gets its own unique comment thread.

It's dirty, but hey, it works! 🤷‍♂️ I'll leave it until I can get a better solution.

Now go on and leave some comments!

I allowed Guest Commenting if you would like to remain anonymous. Though, pre-moderation is enforced by default for guest comments. In other words, guest comments would require my approval to go public.


In Problems When Setting Up WriteFreely Part 2: Firewall, I posed an unanswered question:

Why did netstat not show me processes listening to port 80 & 443, yet I was able to make an HTTP request to port 80?

In that post, I showed a netstat output as follows:

[email protected]:~$ netstat -t4lpn
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN      -                   
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:33060         0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:3306          0.0.0.0:*               LISTEN      -       

I wanted to confirm that my WriteFreely instance was listening to port 80 & 443. But, the above output didn't give me the answer I sought. I didn't see anything listening on port 80, yet I was able to connect to that port successfully!

I only focused on IPv4 at the time because I thought I was connecting to the IPv4 interface of my server. After all, my server wasn't assigned a public IPv6 address. teddyh.dev only has an A record containing the IPv4 address of my server.

Later, I did more research on this seemingly odd phenomenon. I stumbled upon this SO post. Apparently, AF_INET6 sockets can receive connections from IPv4 addresses!

Had I used sudo netstat -tlpn instead, I would have seen the following output:

[email protected]:~$ sudo netstat -tlpn
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN      459/systemd-resolve 
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      707/sshd: /usr/sbin 
tcp        0      0 127.0.0.1:33060         0.0.0.0:*               LISTEN      644/mysqld          
tcp        0      0 127.0.0.1:3306          0.0.0.0:*               LISTEN      644/mysqld          
tcp6       0      0 :::22                   :::*                    LISTEN      707/sshd: /usr/sbin 
tcp6       0      0 :::443                  :::*                    LISTEN      140750/writefreely  
tcp6       0      0 :::80                   :::*                    LISTEN      140750/writefreely

This output gave way more insights than the netstat -t4lpn. The last 2 lines showed that WriteFreely was listening to :::80 & :::443. That would have given me some clue about this probable relationship between IPv6 sockets and IPv4 client addresses.

To further demonstrate this compatibility, I used telnet to open a TCP connection to my server.

My home PC:

╭─teddy@teddy-ubuntu ~ 
╰─$ telnet teddyh.dev 443                                                                                                                                                                                    130 ↵
Trying 54.151.219.0...
Connected to teddyh.dev.
Escape character is '^]'.

My blog server:

[email protected]:~$ sudo netstat -t6apn
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp6       0      0 :::22                   :::*                    LISTEN      707/sshd: /usr/sbin 
tcp6       0      0 :::443                  :::*                    LISTEN      140750/writefreely  
tcp6       0      0 :::80                   :::*                    LISTEN      140750/writefreely            
tcp6       0      0 172.31.29.106:443       A.B.C.D:36134     ESTABLISHED 140750/writefreely  

I have redacted my home IP address above to A.B.C.D. Notice that both the local & foreign addresses are IPv4. 172.31.29.106 is the private IPv4 address of my server, NAT'ed from the public IPv4 address 54.151.219.0 shown in the output of the telnet command.

The above experiment proves that AF_INET6 sockets are compatible with IPv4 addresses. That said, that compatibility can be overridden by setting the IPV6_V6ONLY option, as pointed out in the linked SO post.


This is a two-part series. Read part 1 here.

In my previous post, I ran into an HSTS issue. I bought a domain name that's apparently HSTS preloaded. That means whenever I use a modern browser to visit my blog, it will be strictly loaded over HTTPS. My plan to simply load my blog over HTTP was foiled. To work around that, I ran WriteFreely in standalone mode, which supports SSL out-of-the-box. With that, I solved the problem. I can now visit https://teddyh.dev!


Or so I thought.

At least, until I found out that I still couldn’t load my blog. I was still seeing the same ERR_CONNECTION_TIMED_OUT in my browser. How come?

╭─teddy@teddy-ubuntu ~ 
╰─$ curl -vvv https://teddyh.dev
*   Trying 54.151.219.0:443...
* TCP_NODELAY set
* connect to 54.151.219.0 port 443 failed: Connection timed out
* Failed to connect to teddyh.dev port 443: Connection timed out
* Closing connection 0
curl: (28) Failed to connect to teddyh.dev port 443: Connection timed out
╭─teddy@teddy-ubuntu ~ 
╰─$ curl -vvv http://teddyh.dev
*   Trying 54.151.219.0:80...
* TCP_NODELAY set
* Connected to teddyh.dev (54.151.219.0) port 80 (#0)
> GET / HTTP/1.1
> Host: teddyh.dev
> User-Agent: curl/7.68.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 302 Found
< Content-Type: text/html; charset=utf-8
< Location: https://teddyh.dev/
< Date: Tue, 14 Jun 2022 08:05:09 GMT
< Content-Length: 42
< 
<a href="https://teddyh.dev/">Found</a>.

* Connection #0 to host teddyh.dev left intact

The fact that I got a proper response over http should indicate that my standalone WriteFreely setup was working as intended. Indeed, the documentation said that WriteFreely should redirect http connections to https. However, I couldn't see any log of the http connections I made. Either WriteFreely doesn't log the http redirects, or something else was handling the redirects.

I was pretty sure that WriteFreely was the one handling the redirects. However, I had to verify. I tried checking for open ports using netstat:

[email protected]:~$ netstat -t4lpn
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN      -                   
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:33060         0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.1:3306          0.0.0.0:*               LISTEN      -                   

I saw nothing was listening to port 80 & 443. I scratched my head. How come? Earlier, I was able to get a response from port 80! (That shall be the topic of my next post)

Anyways, I tried another method to verify — I stopped WriteFreely and tried curling again:

╭─teddy@teddy-ubuntu ~ 
╰─$ curl -vvv http://teddyh.dev
*   Trying 54.151.219.0:80...
* TCP_NODELAY set
* connect to 54.151.219.0 port 80 failed: Connection refused
* Failed to connect to teddyh.dev port 80: Connection refused
* Closing connection 0
curl: (7) Failed to connect to teddyh.dev port 80: Connection refused
╭─teddy@teddy-ubuntu ~ 
╰─$ curl -vvv https://teddyh.dev
*   Trying 54.151.219.0:443...
* TCP_NODELAY set
* connect to 54.151.219.0 port 443 failed: Connection timed out
* Failed to connect to teddyh.dev port 443: Connection timed out
* Closing connection 0
curl: (28) Failed to connect to teddyh.dev port 443: Connection timed out

Seeing how my http connection was refused, I concluded that indeed WriteFreely was the one redirecting http->https. Still, it puzzles me why I couldn't connect to the https port.

I hadn't notice this at the time, but there was a subtle difference in the errors reported above: Connection refused vs Connection timed out. That should've been a clue.

It took me some time and some visits to Google & StackOverflow before it dawned on me: maybe a firewall was blocking https requests!

I checked my iptables and saw the word ufw scattered around. Aha! I remembered that I had once played with ufw on this server — as part of Linux Upskill Challenge. I've completely forgotten it because that was ages ago.

Sure enough, I saw https traffic was treated with the ufw default incoming DENY policy (since only ssh and http traffic is allowed):

[email protected]:~$ sudo ufw status
Status: active

To                         Action      From
--                         ------      ----
22/tcp                     ALLOW       Anywhere                  
80/tcp                     ALLOW       Anywhere                  
22/tcp (v6)                ALLOW       Anywhere (v6)             
80/tcp (v6)                ALLOW       Anywhere (v6)             

Once I configured ufw to allow https traffic, I was able to visit my blog on my browser! :)

This ends the two-part series on problems I faced when setting up my WriteFreely instance. Writing these posts have been truly rewarding! I learned a new command (tcpdump), new tricks with old commands, and I gained a better understanding of Linux & networking. It made me better at troubleshooting too.

Running my own blog and writing down my learnings have paid huge dividends! :)

Next, I think I'll cover some things I learned in the process of troubleshooting:

Stay tuned!


PS:

It didn't cross my mind at the time, but I could've ssh into my server and curl my local port 443:

[email protected]:~$ curl -I https://teddyh.dev --resolve 'teddyh.dev:443:127.0.0.1'
HTTP/2 200 
content-type: text/html; charset=utf-8
date: Tue, 14 Jun 2022 08:52:53 GMT

Had I done so, I would have been assured that my WriteFreely setup was working correctly. It would have also pointed me to look at my firewall rules sooner.

On a side note, troubleshooting a confluence of issues can be challenging. It's easy to assume that A is caused by B and B only. But, it could've been caused by B, C, and D. I guess multi-factor problems like these reflect life more accurately, wherein issues are often caused by multiple colluding variables.