Serving a Web App Like a Home Cooked Meal

Let’s say you wrote a little web-app for your own use, like a home cooked meal. You made it just for you and your family and maybe a few friends and you want to serve it so that they can access it from their devices. How hard could that be?

It turns out there are gotchas everywhere.

The Goal

As the developer of Dropserver I think about how people might host tiny personal web services a lot. My goals for them are:

  • Can be implemented with minimal technical skills.
  • Keep security risks to a minimum.
  • Don’t spend money or not much at least.
  • Avoid dependence on 3rd party services. They come, they go, they kill their free plan, they change their prices, they sell your data, etc…
  • Preserve their privacy. Anything that forces them to reveal information about this app or themselves to the outside world should be avoided. This is a thing between them and their family and friends, there is no need for it to show up in a public record of any kind.
  • Use the web browser’s full capabilities (an HTTPS connection is required)
  • Convenience. None of the family members are going to jump through too many hoops.

The sad reality is that the way the web works nowadays, succeeding with these goals is challenging. The web has evolved to cater to large centralized platforms, and it shows in the constraints imposed on people who want to host a simple little thing.

The Three Requirements

You need three things to access your self-hosted web-app:

  • network: you need an IP that is reachable by your web browser.
  • name: a name to enter into the address bar. This can be a domain name or a computer name on an intranet.
  • certificate: a TLS certificate that the browser deems valid for that address. Without that you lose a lot of important capabilities.

These three things relate to each-other and must all be satisfied. Let’s dive in.

The Network

You can reach any computer that has a public IP from any computer connected to the internet.

Easy, right? We’re done here.

Not quite.

Getting a public IP is easy: you are probably using one to read this. But the IP you get with your home internet connection is not likely a fixed IP. Meaning it might change at any time, which is not ideal for a server.

You could get a fixed public IP from your internet service provider. Unfortunately it also costs money 💸 as it’s often viewed as a business product.

You could also use IPv6. Getting one of those is free, but IPv6 is not universally supported, and is a bit harder to get to work with legacy home hardware.

If you do use a fixed IPv4 at your home, you’ll likely have to use port forwarding to send the requests from your router to your little app server. At least that’s well documented.

Instead of a fixed IP, you can use a dynamic DNS service, which might be free (and a bit shady) or spendy 💸.

The public network is a bit much

Regardless of how you funnel internet traffic to your little server, there is a problem with a public IP: it is public. Everybody can reach your IP. The moment you set up a server to listen on a public IP, as you would on Linode or other cloud hosting provider, it gets assailed by bots trying to guess your password, and probe to find a vulnerable install of Wordpress and countless other things. Their goal is to be the first to find a way into your machine, take it over, and make it do bad things. Fun.

To avoid getting hacked you must make sure everything is configured to be as locked down as possible. This is doable but it takes a lot of experience and time to do right. And then you should diligently update your software to patch vulnerabilities which are dropping faster and faster in the age of AI.

Even if you do get all that right you may still have your day ruined. While some bots try to break into your server, others will sometimes try to clog it with so many requests that it becomes stuck and is unusable. Then you might get an email asking for a ransom to get the bot attack to stop. More Fun. To be fair, these attacks are usually pointed at bigger websites and businesses, but it is part of the reality of being connected to the public internet. One thing that is more and more common: AI crawlers are causing some sites to go down due to the huge traffic they generate.

Protection on the public Internet

If there is a pain there is a pill, and services have sprung up to help people protect themselves from bot attacks. Some are quite expensive, but Cloudflare offers great DDoS protection, and it’s even free for basic protection. You hide your server behind their massive network and you can worry less. That’s nice. It’s a dependence on a third-party service, but it may be worth it.

Unfortunately CF can have outages: October 2023, June 2022 July 2020. It’s a giant complex network they run, so it’s not surprising this happens. After two outages in 2025, they decided to do something about it.

Another downer about CF: its bot blocker blocks lesser-known clients that self-hosters might enjoy using: alternative browsers, RSS readers, etc…

Cloudflare can definitely be helpful here, but wouldn’t it be better if we could just not have a completely public IP? This is a private app after all, and it’s only going to be accessed by select few known people.

Private IP

You could host on your Local Area Network but your app would only be accessible when users are connected to your Wifi. That’s sometimes OK, but it’s limiting. Typically you’ll want to have access no matter where you and your users are in the world.

To get past that, you could create a virtual private network using Wireguard for example. WG is fantastic (if you can manage to configure it). It allows you to connect two computers as if they were on the same network! So if you set up WG on your phone you can access your other machines that have WG installed as if they were all on the local network.

Brilliant! We’re done.

Not.

The Domain Name

If you are on a LAN or Wireguard tunnel, you can reach the address simply by typing the IP into the address bar. That works but it’s not convenient: people prefer typing names. Your options are:

  • Change the /etc/hosts file (or do something equivalent on all the devices you want to access this from
  • Set up your own DNS server. This is not trivial for people who are not network admins. But even if you get past that, every device has to be configured to use that DNS server. This means that you can’t easily give access to your app to a guest.
  • Buy a domain for internal use only. This costs money and the DNS records will show that you have an intranet. This is tolerable but not ideal.

None of these sound great, but before we go on we have to consider the third requirement: the TLS certificate.

The TLS Certificate

For your app to benefit from all the functionality that a browser has to offer, and in particular those that make the app feel “app-like”, like notifications, use of the built-in cameras, and many more, you must serve the app over a secure connection. For that you need to have a TLS certificate signed by a Certificate Authority (CA) that the browser recognizes.

Without getting into the weeds, a certificate allows a browser to verify that a connection to a server does indeed emanate from that server. There are different ways of obtaining a certificate, but there are three main categories for the purpose of this post:

  • a self-signed certificate
  • a cert signed by your own CA
  • a cert signed by a recognized CA

In the case of a self-signed cert, every time you visit the site the browser will give a big warning, and you have to click a few things to get through. Browsers really don’t want you trusting “untrusted” certificates so they don’t make it easy. It’s inconvenient at best, and teaching your family members to trust invalid certs may be bad.

You could set up your own certificate signing authority and generate certs from that. However each device will have to be told about the CA. So that’s a double-pain: maintaining your own CA is yet another piece of infrastructure you’d rather not deal with, and you’ll have to somehow add that CA to every device of every user who will touch the app. And guess what? That too was not designed to be easy.

If you use a broadly recognized CA (one that is already known to your browser, you won’t need to add it) then they will only issue certs for a domain that you own! So looking back at the previous section, that means we can’t just edit /etc/hosts or run our own DNS server. We’re buying a domain.

They also add the address of the certificate you requested to a publicly readable log for some reason (it’s actually for a good reason). Do you remember that bit about bots attacking anything connected to the internet? Having a list of hosts like that is not helpful to staying under the radar.

Public name, private IP

I mentioned above that Wireguard lets two computers talk to each other from anywhere without using public IPs. It turns out it’s possible to have a domain name point to a private IP. That way, even though every bot knows your IP, none of them can touch it because they do not have your WG keys. Safety!

Your web browser on the other hand is none the wiser. If you have WG set up on your device, you enter the public DNS name of your app, and it resolves to a private IP. You can get proper TLS certificates without running either a DNS server or your own CA.

Tailscale and friends

Unfortunately the approach above is quite a bit of work to set up correctly. Luckily companies like Tailscale do all the work for you. It’s a dependency on a third-party, but one I’m willing to along with. See my guide how to evaluate third-party services for use in home hosting.

Tailscale has been great for me, but I’ve heard about battery drain issues on some people’s phones. I’ve also heard complaints about needing to turn TS off to run a VPN. Having to turn on Tailscale on a phone just to use an app is enough of a hurdle to stop someone using the app.

A further problem of having a Tailscale-only setup: there is little chance of inviting someone outside your immediate circle to be a user on your app. I was able to set up Tailscale on my wife’s phone, but I can’t imagine going through that process to set up an acquaintance’s phone. You could turn on Tailscale funnel, but then you’re back to being publicly reachable, and every bot and sicko can start badgering your instance.

(I asked Tailscale if they could implement OAUTH on their funnel product, so that only authenticated requests would get through, but I haven’t seen it yet.)

Summing Up

So here we are. We thought it would be fun and easy to host a little web app for ourselves and our friends, but instead we learned a lot more about how the internet works than we ever wanted to. And after all that, we are faced with a long series of decisions on how to do things with no clear winner.

Little by little some of this might improve, some specs might evolve, and things that were hard before may become easier. But this moves slowly.

For now it’s all pretty bad.

Aerospace Engineer turned sofware developer and bootstrappin' entrepreneur.