You could also reject unused query parameters. For top-level resources, this will break other sites that link to you and add their own query string parameters (I’ve done it anyway), but for subresources this is absolutely fair game.
I maintain it’s an abuse. I am willing to accept longer target URIs… so long as they don’t contain a question mark. /no-query-strings, which I accept, is longer than /?, which I decline. And without question it’s against the spirit of 414. As RFC 9110 §15.5.15 puts it:
The 414 (URI Too Long) status code indicates that the server is refusing to service the request because the target URI is longer than the server is willing to interpret. This rare condition is only likely to occur when a client has improperly converted a POST request to a GET request with long query information, when the client has descended into an infinite loop of redirection (e.g., a redirected URI prefix that points to a suffix of itself) or when the server is under attack by a client attempting to exploit potential security holes.
(Incidentally, I encourage people to read the specs in cases like these rather than stopping at MDN. The specs aren’t as fearsome as people often expect, and then you have the normative answer. MDN contains non-normative descriptions.)
Even with the text in the RFC I'm inclined to agree with zymhan from the HN thread: there seems to be some leeway in how you can interpret the text and allow to have different length limit depending on context.
The word "server" carries weight here. What is the spirit, please do enlighten me, if there is absolutely no guarantee that example.com/a/b/c.html and example.com/a/b/c.png are being served by the same server.
Obviously, if some specific client's behavior is broken, such as taking that limit and then enforcing it per what, per subdomain? or domain? (or tld?) you'd might care about that population. But you'd be plain wrong to claim such client is aligned with that RFC's spirit.
My inner HTTP pedant says that the most specific status that reasonably matches what you're trying to do is 422 Unprocessable Content, which at least one spec says can be used for syntactically correct but "semantically erroneous" requests.
Failing that, a generic 400 Bad Request would be what I'd fall back to.
Agreed. I've took a look also at RFC 7231, RFC 2616, Wikipedia, and my own experience. Your choice seems optimal; it's better than a blank 400 because it clearly points out which part of the request is to blame.
An app is shipped only as a docker container, but it never gets rebuilt for security updates for packages inside the docker container - only when a new app update is released.
An app is shipped only as a docker container, making backup harder - as a self hoster, my central Postgres setup etc already has barman. Doing new backup setups for Postgres or MySQL in docker is unfun. If the author uses some random nosql DB I am not familiar with, it’s even more unfun. No hobby docker setups that I have ever seen document anything around backup & recovery.
Then some part of the deployment breaks in a way the original author didn’t anticipate (eg I had this last night where Nextcloud AIO’s backup process stopped working). So now, I get the fun of wading into their docker setup to try to figure out what broke and how to fix it, wasting more time than setting up hosting by hand would have done.
++. At some point, every self-hoster went to docker-compose and what you mention is part why I dislike it so much.
Also it bothers me that you'll likely be running containers that use quite different base images, so if you need to dig, you'll need to figure out how different distros work. (My personal infra is RHEL and Debian, so I need to be familiar with one distro too much already.)
Using any single stack solves most of this by itself, it doesn't matter so much what the stack actually is (although LAMP does have some features that are particularly convenient here).
This is the disadvantage of heterogenous tooling. It's nice to be able to write your application in the language that makes most sense to you. It probably would be more convenient for users (here people with homelabs) if everyone standardised on a single stack, but which stack? How do you convince everyone to suck it up and write PHP, say, or use MySQL?
Not so sure. It removes the need for a reverse proxy simply because you are using some other protocol between the web server and the application, whether that's mod_php, FastCGI, or something else. Once you're using Apache, though, it's just as easy to configure reverse proxying as it is any of those. I don't think the LAMP stack addresses any of the caching issues (beyond just saying use mod_cache), sidesteps the SPA complexity issue, or addresses the database extension for full-text-search.
I do think, as an old-school Apache admin who only uses it on a hobby basis these days, it is a lot simpler to do a lot of these things once you already have them set up for one. And then you add valkey and so on, and once you set it up for one, it's there for the next one, and so forth.
I think the way the LAMP stack was generally deployed makes the reverse proxy issue go away. The way I remember the LAMP stack being sold is this: as a customer, I'd subscribe to a service that gives me a S/FTP directory I can upload my files to, and whatever directory they've given to me is configured as the webroot for me, by the hosting provider. Back in the 2000s, there were literally companies offering this service for free.
Regarding caching, I'd argue that the LAMP stack pushes the caching issue to the application layer. Which is ugly, sure, but we made it work. Take the Drupal framework for example. Drupal ships with a cache layer that caches full pages for logged-out users, and portions of pages, such as Views (Drupal-speak for database queries) for logged in users. Obviously, it's more complicated than the handwavy description I just gave, but it is there. PHP of course has an opcache feature but I can't speak authoritatively about it.
The way I remember the LAMP stack being sold is this: as a customer, I'd subscribe to a service that gives me a S/FTP directory I can upload my files to, and whatever directory they've given to me is configured as the webroot for me, by the hosting provider. Back in the 2000s, there were literally companies offering this service for free.
This is still very much what you will get in modern times as well from many webhosters. And personally speaking, I think it is great! My personal website is just a few php files (since php is also a great templating language), some html and whatever else static all just sitting in a directory.
Honestly, if you just want a personal website I think this is by far the most robust and reliable method that also doesn't require much maintenance and is pretty future proof because it is so dead simple. It is just something that these days is completely overlooked since many younger people haven't actually been exposed to it.
People don't do this though, right? Why would you add any of that?
Just ship a precompiled executable or a docker container with just the necessary bits. Serving static files especially is so trivial that it doesn't make sense to connect up a reverse proxy for them.
The thing is that this example added a lot of unnessecary cruft as "necessary". You don't need reverse proxies, dedicated static file hosting, built-in certificate renewal, or caching at the hobby scale.
Eh, if you want to let anyone else access it (and not invite them into your Tailscale network or however you access your private stuff), you probably want CA-signed certs. Then you would need, or at least welcome, their renewal. And in my experience, all that is easiest when using a simple reverse proxy. Caching and static sites -- full ack.
Sure you want certs, but if you already have a reverse proxy you'll probably prefer to use it instead of a bundled one. And if you don't have one, then you probably don't need one either and can let the server use your certificates directly.
Why doesn't this site work unless I use a VPN? Hmmm...
I offload static files to... a web server. It's the simplest thing to set up, if we don't bother with stuff we don't need, like a reverse proxy or containers.
Why would I have a web app require vinyl, when I could just fast path unauthenticated visitors? We're not as dumb as Wordpress, after all.
Why would I run caddy? Go isn't quite portable yet, and if someone wants to run caddy, they can run caddy and figure it out for themselves.
Why would I run caching middleware? My web app should have some internal concept of caching if it needs one. You build the feature close to where it's being used so that it's not made inefficient by layers in between.
Why would I embed nodejs in to my app? My app should output information that both Javascript can render and that can also be used as simple text. Why complicate everything by putting the Javascript cart in front of the web app horse, instead of the other way around?
Why would I add a reverse proxy to my web app when that's just a fix looking for a problem?
Why would I add a postgresql plugin for everyone when it only speeds up a small percentage of users' use? I'd just let people add the plugin themselves if the default performance isn't good enough for them.
In other words, most of these things only make sense if you like bandwagons and you tend to do things because everyone else seems to do them. After all, you could just run bozohttpd from inetd and eliminate everything else except the app itself.
Here's what I think my ideal stack would look like for a greenfield web app project:
Database in SQLite. For self-hosted apps at small to medium scale, SQLite is more than good enough.
App server written in Rust, designed to run behind a reverse proxy so you can manage your TLS cert using whatever you're already running.
Static assets baked into the Rust binary. No need to even open them, let alone read or explicit mmap; they're just there in virtual memory, and the OS can demand-page them in if needed.
No caching layer. With efficient native code, computing both unauthenticated and common authenticated responses should just be fast enough.
Front-end in Marko 6. Multi-page application built around server-side rendering, with minimal client-side JS (built on fine-grained, compiler-based reactivity) for the things that actually need it. Marko at least theoretically supports server-side adapters other than Node, so should be able to embed V8 in the Rust app server. I think this is the most speculative part.
The result is a single binary that one can run behind a reverse proxy with a very simple Caddy or nginx configuration.
I agree that running a bunch of small web services on a Hetzner box (in a reproducible way) is altogether too much of a pain and too much devops work than I care for. All the solutions that are suggested fall apart within 5-10 minutes of looking at them.
The issue I see here is that in the "professional scale" version, the abstract deployable unit is stops being a container that exposes a port, and becomes a constellation of cloud services, this is a pretty fundamental change with its own tradeoffs. In doing that you are sort of forced to choose between "i want 20 services on one box" and "i want one service on 20 boxes"
I have started building all my personal apps on smallweb.run, and hosting them strictly within my own network. Not even Tailscale. If I want access to something while I’m out, I have a ChatMail email bot that can send me stuff if I need it.
bakkot | 23 hours ago
This article and I have a very different idea of what "hobby scale" means.
chrismorgan | a day ago
You could also reject unused query parameters. For top-level resources, this will break other sites that link to you and add their own query string parameters (I’ve done it anyway), but for subresources this is absolutely fair game.
Sharparam | 15 hours ago
From your block page:
I think you're fine actually, MDN says:
If I end up making web sites again I'm definitely going to consider implementing something similar, so thank you for the inspiration!
chrismorgan | 11 hours ago
I maintain it’s an abuse. I am willing to accept longer target URIs… so long as they don’t contain a question mark. /no-query-strings, which I accept, is longer than /?, which I decline. And without question it’s against the spirit of 414. As RFC 9110 §15.5.15 puts it:
(Incidentally, I encourage people to read the specs in cases like these rather than stopping at MDN. The specs aren’t as fearsome as people often expect, and then you have the normative answer. MDN contains non-normative descriptions.)
(See also https://news.ycombinator.com/item?id=48077786 which discussed the same detail.)
Sharparam | 11 hours ago
Even with the text in the RFC I'm inclined to agree with zymhan from the HN thread: there seems to be some leeway in how you can interpret the text and allow to have different length limit depending on context.
kubanczyk | 7 hours ago
The word "server" carries weight here. What is the spirit, please do enlighten me, if there is absolutely no guarantee that example.com/a/b/c.html and example.com/a/b/c.png are being served by the same server.
Obviously, if some specific client's behavior is broken, such as taking that limit and then enforcing it per what, per subdomain? or domain? (or tld?) you'd might care about that population. But you'd be plain wrong to claim such client is aligned with that RFC's spirit.
ubernostrum | 5 hours ago
My inner HTTP pedant says that the most specific status that reasonably matches what you're trying to do is
422 Unprocessable Content, which at least one spec says can be used for syntactically correct but "semantically erroneous" requests.Failing that, a generic
400 Bad Requestwould be what I'd fall back to.fanf | 3 hours ago
422 is a complaint about the body of the request, not the request URL.
kubanczyk | 12 hours ago
Agreed. I've took a look also at RFC 7231, RFC 2616, Wikipedia, and my own experience. Your choice seems optimal; it's better than a blank 400 because it clearly points out which part of the request is to blame.
fourfourthree | 21 hours ago
This misses the final steps!
An app is shipped only as a docker container, but it never gets rebuilt for security updates for packages inside the docker container - only when a new app update is released.
An app is shipped only as a docker container, making backup harder - as a self hoster, my central Postgres setup etc already has barman. Doing new backup setups for Postgres or MySQL in docker is unfun. If the author uses some random nosql DB I am not familiar with, it’s even more unfun. No hobby docker setups that I have ever seen document anything around backup & recovery.
Then some part of the deployment breaks in a way the original author didn’t anticipate (eg I had this last night where Nextcloud AIO’s backup process stopped working). So now, I get the fun of wading into their docker setup to try to figure out what broke and how to fix it, wasting more time than setting up hosting by hand would have done.
koala | 18 hours ago
++. At some point, every self-hoster went to docker-compose and what you mention is part why I dislike it so much.
Also it bothers me that you'll likely be running containers that use quite different base images, so if you need to dig, you'll need to figure out how different distros work. (My personal infra is RHEL and Debian, so I need to be familiar with one distro too much already.)
dpedu | 14 hours ago
The LAMP stack of old solves most, if not all, of this.
Johz | 11 hours ago
Using any single stack solves most of this by itself, it doesn't matter so much what the stack actually is (although LAMP does have some features that are particularly convenient here).
This is the disadvantage of heterogenous tooling. It's nice to be able to write your application in the language that makes most sense to you. It probably would be more convenient for users (here people with homelabs) if everyone standardised on a single stack, but which stack? How do you convince everyone to suck it up and write PHP, say, or use MySQL?
gcupc | 14 hours ago
Not so sure. It removes the need for a reverse proxy simply because you are using some other protocol between the web server and the application, whether that's mod_php, FastCGI, or something else. Once you're using Apache, though, it's just as easy to configure reverse proxying as it is any of those. I don't think the LAMP stack addresses any of the caching issues (beyond just saying use mod_cache), sidesteps the SPA complexity issue, or addresses the database extension for full-text-search.
I do think, as an old-school Apache admin who only uses it on a hobby basis these days, it is a lot simpler to do a lot of these things once you already have them set up for one. And then you add valkey and so on, and once you set it up for one, it's there for the next one, and so forth.
dpedu | 14 hours ago
I think the way the LAMP stack was generally deployed makes the reverse proxy issue go away. The way I remember the LAMP stack being sold is this: as a customer, I'd subscribe to a service that gives me a S/FTP directory I can upload my files to, and whatever directory they've given to me is configured as the webroot for me, by the hosting provider. Back in the 2000s, there were literally companies offering this service for free.
Regarding caching, I'd argue that the LAMP stack pushes the caching issue to the application layer. Which is ugly, sure, but we made it work. Take the Drupal framework for example. Drupal ships with a cache layer that caches full pages for logged-out users, and portions of pages, such as Views (Drupal-speak for database queries) for logged in users. Obviously, it's more complicated than the handwavy description I just gave, but it is there. PHP of course has an opcache feature but I can't speak authoritatively about it.
creesch | 12 hours ago
This is still very much what you will get in modern times as well from many webhosters. And personally speaking, I think it is great! My personal website is just a few php files (since php is also a great templating language), some html and whatever else static all just sitting in a directory.
I have rambled on about it in previous comments in more detail.
Honestly, if you just want a personal website I think this is by far the most robust and reliable method that also doesn't require much maintenance and is pretty future proof because it is so dead simple. It is just something that these days is completely overlooked since many younger people haven't actually been exposed to it.
landon | 3 hours ago
As long as you're writing your app in the only one language all web apps are built with: HTML with inline PHP, that's still an option
JulianSildenLanglo | a day ago
People don't do this though, right? Why would you add any of that?
Just ship a precompiled executable or a docker container with just the necessary bits. Serving static files especially is so trivial that it doesn't make sense to connect up a reverse proxy for them.
cceckman | a day ago
Just...
I mean the end state was "ship a Docker container". But that doesn't alleviate "the necessary bits" being fairly large (in this example).
JulianSildenLanglo | 17 hours ago
The thing is that this example added a lot of unnessecary cruft as "necessary". You don't need reverse proxies, dedicated static file hosting, built-in certificate renewal, or caching at the hobby scale.
kolja | 16 hours ago
Eh, if you want to let anyone else access it (and not invite them into your Tailscale network or however you access your private stuff), you probably want CA-signed certs. Then you would need, or at least welcome, their renewal. And in my experience, all that is easiest when using a simple reverse proxy. Caching and static sites -- full ack.
JulianSildenLanglo | 16 hours ago
Sure you want certs, but if you already have a reverse proxy you'll probably prefer to use it instead of a bundled one. And if you don't have one, then you probably don't need one either and can let the server use your certificates directly.
johnklos | 16 hours ago
Why doesn't this site work unless I use a VPN? Hmmm...
I offload static files to... a web server. It's the simplest thing to set up, if we don't bother with stuff we don't need, like a reverse proxy or containers.
Why would I have a web app require vinyl, when I could just fast path unauthenticated visitors? We're not as dumb as Wordpress, after all.
Why would I run caddy? Go isn't quite portable yet, and if someone wants to run caddy, they can run caddy and figure it out for themselves.
Why would I run caching middleware? My web app should have some internal concept of caching if it needs one. You build the feature close to where it's being used so that it's not made inefficient by layers in between.
Why would I embed nodejs in to my app? My app should output information that both Javascript can render and that can also be used as simple text. Why complicate everything by putting the Javascript cart in front of the web app horse, instead of the other way around?
Why would I add a reverse proxy to my web app when that's just a fix looking for a problem?
Why would I add a postgresql plugin for everyone when it only speeds up a small percentage of users' use? I'd just let people add the plugin themselves if the default performance isn't good enough for them.
In other words, most of these things only make sense if you like bandwagons and you tend to do things because everyone else seems to do them. After all, you could just run bozohttpd from inetd and eliminate everything else except the app itself.
mt | 13 hours ago
TIL that varnish was renamed to vinyl!
mwcampbell | 12 hours ago
Here's what I think my ideal stack would look like for a greenfield web app project:
openthem, let alonereador explicitmmap; they're just there in virtual memory, and the OS can demand-page them in if needed.The result is a single binary that one can run behind a reverse proxy with a very simple Caddy or nginx configuration.
alper | 7 hours ago
Taking htmx for that last thing would make it a lot less speculative, or not?
alper | 7 hours ago
I agree that running a bunch of small web services on a Hetzner box (in a reproducible way) is altogether too much of a pain and too much devops work than I care for. All the solutions that are suggested fall apart within 5-10 minutes of looking at them.
landon | 3 hours ago
The issue I see here is that in the "professional scale" version, the abstract deployable unit is stops being a container that exposes a port, and becomes a constellation of cloud services, this is a pretty fundamental change with its own tradeoffs. In doing that you are sort of forced to choose between "i want 20 services on one box" and "i want one service on 20 boxes"
marginalia | 16 hours ago
fly | 15 hours ago
I have started building all my personal apps on smallweb.run, and hosting them strictly within my own network. Not even Tailscale. If I want access to something while I’m out, I have a ChatMail email bot that can send me stuff if I need it.