Perl CGI in 2026

Why I use CGI scripts for the backend functionality of this website.

This website is built using an SSG1: all the templates are filled in with their content, and are then saved as static HTML code. SSGs have the massive advantage that they don’t require a heavy server, or any additional network round-trips to fetch the content for the page that my visitors are viewing. Instead of the page downloading a posts list separately, it’s baked into the HTML so it’s already there when the page loads. However, this has the downside that you are now limited to showing the same content on every reload, until you rebuild the website.

As I was putting together this website and getting it ready for publication, there were a couple of things I wanted to add that were inherently dynamic: a hit counter, and a contact form. Today, a lot of people faced with this situation would reach for a web framework like NextJS, perhaps combined with ‘serverless’ functions.2 However, as my site was already nearing completion and I didn’t want to re-write it, I went looking for alternatives, and I found one: Perl CGI scripts.

CGI (Common Gateway Interface) is a web standard first developed in the 1990s that lets web servers (like the one hosting the static site you’re now viewing) call into external programs to process web requests. When a web request comes in for the CGI script, the web server calls the script, giving it information about the request like the parameters, and then responds to the website viewer with whatever output the script produced. Because this protocol is so simple, writing CGI scripts is very straightforward.

Instead of needing a complicated web framework or serverless functions runtime that I’d then be locked into, I can write simple one-file scripts that I can fully understand, with no special execution environment needed. It meant that I could keep the existing static site that I’d already built exactly the same, and add just those few bits of reactivity that I wanted. For example, the visitor counter is implemented using a bit of Perl that increments the counter in the database, and responds with an image rendering the count:

perl
my $dbh = DBI->connect("dbi:SQLite:dbname=$db", '', '', {
    # connection args omitted...
});
my ($count) = $dbh->selectrow_array(
	'UPDATE hits SET n = n + 1 WHERE id = 1 RETURNING n'
);

$cgi->set_response_type('image/png');
$cgi->add_response_header('Cache-Control' => 'no-store');
$cgi->render(data => SegmentedCounter::render($count));

That’s it! The code is maybe 50 lines in total. I did use a simple wrapper library around common CGI tasks, CGI::Tiny, but it’s even possible to just print HTTP output to stdout to use it as a response.

Of course, CGI scripts do have their own downsides, which is why they are rarely used anymore for new projects. The main problem is latency: as the web server has to spawn a new instance of the Perl interpreter every time a request comes in, request responses are slower than with modern paradigms that use a prewarmed worker pool to handle requests.

One of the biggest timesinks here can be Perl needing to load all the included modules over and over again, so I would recommend keeping your scripts as simple as possible and avoiding heavy frameworks. For example, I cut startup time from 94ms to 21ms by pruning the modules used. What we pay for in raw performance though, we get back in readability and simplicity. Highly recommended!

Footnotes

  1. Static site generator. ↩

  2. AWS Lambdas, Cloudflare Workers, Fly Machines, etc. ↩