Open-source project
avleen/bashttpd avatar
avleen/bashttpd

bashttpd spawns a bash process per connection and barely reaches HTTP/1.0

A web server written in bash

1,554 stars204 forksShellMIT

At a glance

What is it?
bashttpd is a web server that is a single bash script, handed incoming sockets by socat or netcat. It serves files, lists directories and does no authentication, and the README says outright that it barely supports HTTP/1.0.
Who is it for?
Use bashttpd when you need to hand a directory to a colleague or a script on a trusted network and nothing more: it is bash with no build step, and the socat fork variant gives you one process per connection. Do not use it for anything reachable from an untrusted network, since the README states it does not support authentication, that its input handling is rudimentary, and that the author would not run it on a public machine.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 91 days ago.
What is it written in?
Mainly Shell, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • avleen/bashttpd1,554 ★ · +0
Star history
Repository2026-10-162026-10-23
avleen/bashttpd1.6k1.6k

0 Stars in the last 7 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

The socket is not the server's job, and that is the whole architecture

bashttpd is a single file named bashttpd at the root of the repository, alongside only LICENSE and README.md. It does not open a listening socket, accept connections or loop. It reads one request from standard input, writes a response to standard output, and exits. Everything else is somebody else's program.

That division is why the requirements list is so short: bash in any recent version, socat or netcat to handle the underlying sockets, and, in the README's phrasing, a healthy dose of insanity. The server is a request handler invoked once per connection by an external multiplexer.

The invocation is where the real behaviour is decided. This is the minimal form:

bash
socat TCP4-LISTEN:8080 EXEC:/usr/local/bin/bashttpd

Read that carefully, because the README immediately flags its consequence: the server exits as soon as the first connection closes, so this form serves exactly one client and then stops listening. Adding the fork option is what turns it into something that behaves like a server:

bash
socat TCP4-LISTEN:8080,fork EXEC:/usr/local/bin/bashttpd

With fork, socat spawns a new process for each incoming connection, which is the only concurrency model in the design. There is no worker pool, no keep-alive reuse and no shared state between requests, because each request gets its own bash interpreter. The netcat route has the same shape:

bash
netcat -lp 8080 -e ./bashttpd

Note that this one uses a relative path, so it only works from the directory holding the script.

First run generates bashttpd.conf, so read it before you serve anything

There is no configuration file in the repository to copy. Running bashttpd for the first time generates a default configuration file named bashttpd.conf, which you then review and edit, and only after that do you run it under socat or netcat. The order in the README's getting started section is deliberate: generate, review, serve.

The repository listing confirms this is the intended shape. Three files, no sample config checked in, no config example directory. Whatever the defaults are, they are produced by the script itself on your machine, which means the generated file is the only documentation of the available keys.

One documented capability is configuration based on the client-specified URI. That is the mechanism that makes the tool more than a static file dumper: the mapping from a request path to a filesystem location lives in configuration rather than in code, so a single running instance can present different content for different paths without any routing logic in the script itself. For anything beyond a demo, that file is the entire attack surface and the entire set of tunable behaviour, and the README does not enumerate what a generated file contains.

The absence of a shipped sample is a genuine gap rather than a stylistic choice. A fork can add one.

MIME types come from /usr/bin/file, so the content type is a guess

The HTTP protocol support section states that bashttpd uses /usr/bin/file to determine the MIME type sent to the browser. That is a design choice with a visible consequence: the Content-Type header is derived from a file inspection heuristic rather than from an extension table the project controls.

In practice that means the server inherits whatever the host's file magic database reports, and it inherits its absence too. On a minimal container image, or any system where the file utility is not installed, the MIME detection has nothing to consult, and the README does not describe a fallback. A reader deploying this into a small image should check for /usr/bin/file before assuming content will be served with a usable type.

The status codes are enumerated and narrow. 200 is returned with valid content. 403 is returned when a directory is not listable or a file is not readable. 400 is returned when the first word of the first line is not GET. That last rule is the clearest statement of the request handling in the whole project: the parser looks at the first token of the first line, and anything that is not a GET is a bad request. There is no mention of HEAD, POST, PUT, DELETE, ranges or conditional requests anywhere in the documentation.

Two of those absences are visible in ordinary browser use. A range request, which a browser issues when you seek within a media file, will not be served correctly. A HEAD request, which some clients use to probe a URL, is not on the accepted list.

It barely supports HTTP/1.0, and the README is blunt about it

The documentation's own assessment of the protocol level is the sentence to anchor any evaluation on: the server does not support Host: headers or other HTTP/1.1 features, and it barely supports HTTP/1.0. The Limitations section adds that it doesn't strictly adhere to the HTTP spec.

The missing Host header is the item with the widest blast radius. Every HTTP/1.1 request carries Host, every HTTP/1.1 server must require it, and it is how a single listener serves several names. A server that ignores it cannot do virtual hosting, and a client that insists on sending it is talking to something that is not really speaking the same protocol.

Combined with the one-shot EXEC model and the absence of keep-alive, the picture is a server built to serve a handful of files to a handful of clients. That is a legitimate thing to want, and the project is candid about it. What it is not is a component to drop in front of anything you care about.

The honest framing is that bashttpd is a demonstration of what the shell can do, packaged so it actually runs. The README lists features in three items: serving text and HTML files, showing directory listings, and allowing configuration based on the client-specified URI. Those three lines are the complete feature set, and the project does not oversell them.

No authentication, and input handling the author declines to defend

The Limitations section lists, first, that it does not support authentication. The Security section is blunter: only rudimentary input handling, and we would not running this on a public machine. The README even quotes its own creator on the risk, warning that anyone who installs it somewhere may come to a bad end.

For a networked service, no authentication plus rudimentary input handling is a combination that has to be treated as a hard boundary rather than a caveat. Anything that binds to a routable interface exposes every readable file under the configured root to anyone who can open a TCP connection, and the input handling is explicitly not hardened.

There is a further property that follows from the architecture rather than from a missing feature. Because socat hands each connection to a fresh bash process, whatever the script does with untrusted input happens in a subprocess with no isolation, and because the configuration maps client-specified URIs onto filesystem paths, path handling is precisely the kind of code that rudimentary input handling describes. A fork that wants to survive contact with an untrusted network would have to fix the URI mapping and the input parsing first, which is most of the script.

Binding to loopback is the mitigation the architecture supports naturally, and the socat example uses TCP4-LISTEN with an explicit port and no interface, so the deployment decision is entirely in the hands of whoever writes the command.

Three files, no releases, and a last push on 2026-07-24

Maintenance facts here are sparse by design of the project. The repository publishes no GitHub releases, so there is no version history to consult and no upgrade path, because there is nothing to upgrade from. The last push was on 2026-07-24, and the repository is not archived.

The licence is MIT, with a LICENSE file at the root. That matters more here than for a larger codebase, because a fork is the realistic adoption path for anyone who needs authentication, a stricter request parser or HTTP/1.1 behaviour, and MIT places no obstacle to making one. No legal advice is offered on how a fork should be distributed.

Contributions go through ordinary pull requests, which the README invites, and the tracked issue surface is the only feedback channel. For a project of three files, that is proportionate. What you do not get is a maintainer's compatibility promise, a test suite to catch a regression, or a configuration reference. The docs directory that a larger project would have is instead a few paragraphs in README.md.

The right conclusion for an evaluator is that bashttpd is worth reading and worth copying, and worth running only in the narrow case where a directory needs exposing on a trusted network.

Editorial conclusion

Use bashttpd when you need to hand a directory to a colleague or a script on a trusted network and nothing more: it is bash with no build step, and the socat fork variant gives you one process per connection. Do not use it for anything reachable from an untrusted network, since the README states it does not support authentication, that its input handling is rudimentary, and that the author would not run it on a public machine. Verify first that /usr/bin/file exists on the target system, because MIME type detection depends on it, and read the generated bashttpd.conf before starting, because the server generates that file on first run.

Frequently asked questions

How do I run bashttpd?

Hand it incoming connections with socat or netcat, for example `socat TCP4-LISTEN:8080,fork EXEC:/usr/local/bin/bashttpd`. Without the fork option the server exits after the first connection closes, so it serves a single client.

What does bashttpd need installed?

The README lists bash in any recent version, socat or netcat for the sockets, and, in its words, a healthy dose of insanity. Content type detection also relies on /usr/bin/file being present on the system.

Does bashttpd support HTTPS or authentication?

Authentication is explicitly listed as unsupported, and the security note says input handling is rudimentary and that the author would not run it on a public machine. HTTPS is not mentioned anywhere in the README.

How do I configure bashttpd?

The first run generates a default file named bashttpd.conf in the working directory, which you then edit before serving. Configuration can map the client-specified URI, and the repository ships no sample config file.

Official sources

  1. avleen/bashttpd on GitHub
  2. Issues
  3. License: MIT
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/avleen-bashttpd.svg)](https://hysenlabs.com/projects/avleen-bashttpd)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/avleen-bashttpd.svg)](https://hysenlabs.com/projects/avleen-bashttpd)

Was this useful?