Web tool running shell command in lighttpd

How could I run simple web tool, which would run shell command and return the response?
For example, on

the server would run “echo ‘running tool $NAME’” and therefor return “running tool tool-A” to the browser.

Is there a way how to do it using build-in tools (lighttpd?)

Thank you.

Yes, you can use lighttpd to do this.
In fact, LuCI is configured to run as CGI. See /etc/lighttpd/conf.d/luci.conf

However, note that your CGI program will run as the same user as the lighttpd web server, which is running as root on Turris Omnia. To run your tool(s) as an alternate user, you can use mod_fastcgi or mod_scgi or mod_proxy to connect to a daemon running as a different user.

I want to run it as root. I want just hint for hello world, to be able to continue myself. I tried to copy the luci settings and change it for my purpose, but with no success (yes, I restarted lighttpd after change). Now, I have /tools/test.sh file with echo ‘hello’ inside. When I try to access it (, it says 404. But more, I need is to receive the parameters somehow if possible (?name=tool-A).

# lighttpd include file for TOOLS

## Set CGI paths
cgi.assign += (
        "/tools/test.sh" => ""

## Set aliases to LuCI install directory
alias.url += (
        "/tools/" => "/www/tools/"

For start, I would just like to have URL ( where would be bash script (script.sh) on server, executed by lighttpd and result returned to the browser. So if echo “hello” would be in the script.sh, I would expect to see “hello” when entering the URL in browser.

Sounds like you’ve never heard of CGI and how to write a CGI script. i recommend using a search engine for “CGI shell script example”

As for the lighttpd config, it is simpler to put your test.sh in /www/cgi-bin/test.sh and use cgi.assign += (
"/cgi-bin/test.sh" => “”

Also, make sure test.sh is executable (chmod a+x)

1 Like

Never heard of. I will read about it and try. I already found out, that it works under cgi-bin, but I would like to be able to assign another folder. And I still dont know how to read query string (?param=value) from url.

Your alias rule should work, but you’ll get a 404 if you did not create /www/tools/tools.sh. You also need to make sure tools.sh is executable.

And, yes, you would do well to find out more about CGI and the QUERY_STRING environment variable.

So, I tried it again and I tried to do it as simple as possible to mitigate all possible mistakes. I edited luci.conf to make /www/api dir a CGI dir and I put the same script inside. Now, the results are these:

Any idea what’s wrong?

So, now I know, how CGI works and I set lighttpd to listen on one more port just to point in into my tools folder, to keep it separated from the rest (luci). But, I cannot call it without cgi-bin in URL. Whatever else I set in alias.url, it says 404. Any idea why is that and how to solve it? I would like to call (for example).

This is my current settings (working)

$SERVER["socket"] == ":81" {
    server.document-root        = "/www-api/"

    ## Set CGI paths
    cgi.assign += (
        ".sh" => "/bin/bash",
        ".lua" => "/usr/bin/lua",
        ".py" => "/usr/bin/python"

    ## Set aliases to scripts directory
    alias.url += (
        "/cgi-bin/" => "/www-api/cgi-bin/"

You could try URL rewrite if it works:


1 Like

Ok, will try that. But still. No idea why it’s not possible to just do this?:

alias.url += (
    "/cgi-bin/" => "/www-api/some-dir/"

Have you checked the logs if there are any errors: /var/log/lighttpd/error.log

No error in error log (just server started / stopped). I think its a feature for CGI on lighttpd to be able to run just under cgi-bin. Maybe some securoty feature. I don’t have any other explanation.

I think its a feature for CGI on lighttpd to be able to run just under cgi-bin. Maybe some securoty feature. I don’t have any other explanation.

Since you don’t have any explanation, you probably should refrain from making such unsubstantiated statements, and not only because they are absolutely wrong, and not only because you did not know what CGI was when you first started this post. It reflects poorly on you that you blame the software when you are unable to get something to work, especially when that something is new to you.

As one of the lighttpd developers, I can authoritatively tell you that in a stand-alone config, mod_alias and mod_cgi work as I have described. I say stand-alone config because /etc/lighttpd/conf.d/luci.conf contributes only one part of the lighttpd config running on your Turris.

Running the following will show you what lighttpd sees as your entire config (after all include files have been read and evaluated): /usr/sbin/lighttpd -p -f /etc/lighttpd/lighttpd.conf | less

After doing that you might find that your issue is the generated config from foris (/etc/lighttpd/conf.d/foris), which intercepts everything not explicitly excluded. You’ll have to edit /usr/share/foris/lighttpd-dynamic-conf to carve out any customizations you want.

Aside: Personally, I am not a fan of that foris config and will probably submit some patches to CZ-NIC making foris run as a CGI program, instead of as a fastcgi server. However, Foris running as a CGI is much slower and takes almost 4 seconds to return pages, instead of about 1 second, but I don’t run Foris that frequently, and prefer LuCI, anyway. Also, foris + nuci (not LuCI) are using over 25 MB memory and the bottle server is waking up every 1/2 second from select(). All for something almost never used after initial configuration.


Good to know, that it’s not a feature. I tried to find some intercepting command / setting, but I couldn’t find any. I assume it’s because of me, so I ask you, if you could point me in the result of the command you wrote. I expected something what would overwrite the cgi.assign variable (=) instead of adding (+=), but I don’t see it. What I care about now is the “$SERVER[“socket”] == “:81” {” block. I tried to separate my settings from the original by assigning it to another port, which is better for my purpose anyway. As you can see, I tried to alias the same path by two different aliases. But only “cgi-bin” works. Could you please tell me, what I don’t see? Where could I find the foris intercepting settings? Thank you for being patient with me :slight_smile:

config {
    var.PID                        = 17178
    var.CWD                        = "/"
    ## MIME TYPES (I removed them from output, because the size is not friendly for forum post)
    server.document-root           = "/www"
    server.upload-dirs             = ("/tmp")
    server.errorlog                = "/var/log/lighttpd/error.log"
    server.pid-file                = "/var/run/lighttpd.pid"
    index-file.names               = ("index.php", "index.html", "index.htm", "default.htm", "index.lighttpd.html")
    static-file.exclude-extensions = (".php", ".pl", ".fcgi")
    server.modules                 = ("mod_alias", "mod_cgi", "mod_fastcgi", "mod_setenv")
    var.foris.bin                  = "/usr/bin/foris"
    var.foris.flags                = "-s flup"
    var.foris.scriptname           = "/"
    cgi.assign                     = (
        "/cgi-bin/luci"     => "",
        "/cgi-bin/schnappi" => "",
        "/api/schnappi"     => "",
        # 3
    alias.url                      = (
        "/static/"                     => "/usr/lib/python2.7/site-packages/foris/static/",
        "/plugins/diagnostics/static/" => "/usr/share/foris/plugins/diagnostics/static/",
        "/cgi-bin/"                    => "/www/cgi-bin/",
        "/luci-static/"                => "/www/luci-static/",
        # 5

    $SERVER["socket"] == "[::]:80" {
        # block 1

    } # end of $SERVER["socket"] == "[::]:80"

    $SERVER["socket"] == ":81" {
        # block 2
        server.document-root = "/www-api/"
        cgi.assign           = (
            ".sh"  => "/bin/bash",
            ".lua" => "/usr/bin/lua",
            ".py"  => "/usr/bin/python",
            # 3
        alias.url            = (
            "/api/"     => "/www-api/cgi-bin/",
            "/cgi-bin/" => "/www-api/cgi-bin/",
            # 2

    } # end of $SERVER["socket"] == ":81"

    $HTTP["url"] =~ "^/(?(?<!/)/|)(?!static|cgi-bin|luci-static|plugins)" {
        # block 3
        fastcgi.debug  = 0
        fastcgi.server = (
            "/" => (
                "python-fcgi" => (
                    "socket"              => "/tmp/fastcgi.python.socket",
                    "bin-path"            => "/usr/bin/foris -s flup",
                    "fix-root-scriptname" => "enable",
                    "check-local"         => "disable",
                    "max-procs"           => 1,
                    # 5

    } # end of $HTTP["url"] =~ "^/(?(?<!/)/|)(?!static|cgi-bin|luci-static|plugins)"

    $HTTP["url"] =~ "^/cgi-bin/luci" {
        # block 4
        setenv.add-response-header = (
            "X-Frame-Options" => "SAMEORIGIN",

    } # end of $HTTP["url"] =~ "^/cgi-bin/luci"

    $SERVER["socket"] == ":443" {
        # block 5
        ssl.engine  = "enable"
        ssl.pemfile = "/etc/lighttpd-self-signed.pem"

    } # end of $SERVER["socket"] == ":443"

    $SERVER["socket"] == "[::]:443" {
        # block 6
        ssl.engine  = "enable"
        ssl.pemfile = "/etc/lighttpd-self-signed.pem"

    } # end of $SERVER["socket"] == "[::]:443"

    $HTTP["scheme"] == "https" {
        # block 7

    } # end of $HTTP["scheme"] == "https"

I think he is referring to this line:

$HTTP["url"] =~ "^/(?(?<!/)/|)(?!static|cgi-bin|luci-static|plugins)" {

If I read it correctly it will capture anything that doesn’t have static, cgi-bin, luci-static or plugins in the request path in the first or second segment.

1 Like

Ahh. Thank you. Now I get the complete picture. I missed that.

The question:

I want to run bash (or python, lua) script using http access ( and return response to the browser.

The script could be this

echo "Hello world"

The solution:

  • Create folder for the application (/www-api/)

  • Create file script.sh with this content

    echo "Content-Type: text/html"
    echo “”

    echo “Hello World”

  • edit file /usr/share/foris/lighttpd-dynamic-conf

add “api” uri to exceptions on this line:

\$HTTP[\"url\"] =~ \"^\" + var.foris.scriptname + \"(?(?<!/)/|)(?!static|cgi-bin|luci-static|plugins)\" {\n\


\$HTTP[\"url\"] =~ \"^\" + var.foris.scriptname + \"(?(?<!/)/|)(?!static|cgi-bin|luci-static|plugins|api)\" {\n\

That’s because foris takes all requests except those specified here. I want to access my scripts on, so I add “api”.

  • Edit /etc/lighttpd/lighttpd.conf

Add this block somewhere after the :80 settings:

$SERVER["socket"] == ":81" {
    server.document-root        = "/www-api/"
    ## Set CGI paths
    cgi.assign += (
        ".sh" => "/bin/bash",
        ".lua" => "/usr/bin/lua",
        ".py" => "/usr/bin/python"
    ## Set aliases to API directory
    alias.url += (
        "/api/" => "/www-api/"

That way I set lighttpd to listen on port 81, with document root in /www-api/, running bash, lua and python scripts and with alias /api pointed into /www-api dir.

  • restart lighttpd and it should be set.

/etc/init.d/lighttpd restart

Now you can access the file on


I hope my lame question would not be off topic - how is this different/better from something like https://github.com/maestrano/webshell-server or http://web-console.org/ ?

Or purpose of this to be able to “call” any script via url either manualy or from different programs/pages etc ?

The real use for this is to be able to access certain information from host on lxc container. Therefore the port on host on which the webserver listens is accessible only on local network and only from certain IP (the lxc container).