Apache – Create a hello world CGI app on CentOS 7

Common Gateway Interface (CGI) is a protocol that let’s you run custom scripts via the web. It’s not as commonly used as before, but you still need to know this as part of the RHCE exam objectives.


You can find all my latest posts on medium.

In Apache, there is a default folder where you can place scripts in, which will then get handled by the CGI protocol. This default folder is declared in the main Apache config file, here’s the relevant extract:

    # Redirect: Allows you to tell clients about documents that used to
    # exist in your server's namespace, but do not anymore. The client
    # will make a new request for the document at its new location.
    # Example:
    # Redirect permanent /foo

    # Alias: Maps web paths into filesystem paths and is used to
    # access content that does not live under the DocumentRoot.
    # Example:
    # Alias /webpath /full/filesystem/path
    # If you include a trailing / on /webpath then the server will
    # require it to be present in the URL.  You will also likely
    # need to provide a  section to allow access to
    # the filesystem path.

    # ScriptAlias: This controls which directories contain server scripts.
    # ScriptAliases are essentially the same as Aliases, except that
    # documents in the target directory are treated as applications and
    # run by the server when requested rather than as documents sent to the
    # client.  The same rules about trailing "/" apply to ScriptAlias
    # directives as to Alias.
    ScriptAlias /cgi-bin/ "/var/www/cgi-bin/"

Here we can see the ScriptAlias setting. This setting maps the part of a URL to a directory path on the machine. We can drop scripts into /var/www/cgi-bin/, and then we should be able to access them using the ‘/cgi-bin/’ alias in the url, e.g. something like

We now need to check that the cgi module is running:

[root@webserver cgi-bin]# httpd -M | grep cgi
 proxy_fcgi_module (shared)
 proxy_scgi_module (shared)
 cgi_module (shared)
 fcgid_module (shared)

Since our box is in SELinux enforcing mode, Theres a SE boolean that needs to be enabled to allow the use of CGI scripts:

[root@webserver cgi-bin]# getsebool -a | grep -i cgi
git_cgi_enable_homedirs --> off
git_cgi_use_cifs --> off
git_cgi_use_nfs --> off
httpd_enable_cgi --> on

In case it wasn’t enable then you can enable it (p)ersistantly by running:

$ setsebool -P httpd_enable_cgi 1

Now to try out CGI, I created the following hello world bash script:

[root@webserver cgi-bin]# ls -lZ /var/www/cgi-bin/
-rwxr-xr--. apache apache unconfined_u:object_r:httpd_sys_script_exec_t:s0 /var/www/cgi-bin/
[root@webserver cgi-bin]# cat /var/www/cgi-bin/
echo 'Content-type: text'
echo 'hello world'

We added the Content-Type setting so that the web browser know hot to interpret the content. Also notice that the script needs to have a specific security type attribute ‘httpd_sys_script_exec_t’.

Now let’s test out this script in the bash terminal first:

[root@webserver cgi-bin]# /var/www/cgi-bin/
Content-type: text

hello world

Now you can try accessing this link using firefox/chrome/curl, e.g.:

[root@webserver cgi-bin]# curl http://localhost/cgi-bin/

hello world

Using a non standard CGI folder

If you want to change the CGI folder to a different location, then you need to take 4 steps:

  1. Create the new folder, and give Apache ownership of this folder
  2. Place scripts into the new folder
  3. Update SELinux configurations to support the new cgi script location
  4. Update the main Apache config, or create/edit vhost file
  5. Restart httpd daemon

Now let’s create the new cgi folder:

[root@webserver cgi-bin]# mkdir /var/web_scripts

Next we create a dummy script and :

[root@webserver cgi-bin]# cat /var/web_scripts/
echo 'Content-type: text'
echo 'hello world - this time we are in non-standard folder'

Next we check if the ownership, permissions, and security context looks correct:

[root@webserver cgi-bin]# ls -lZ /var/ | grep web_scripts
drw-r--r--. root root unconfined_u:object_r:var_t:s0   web_scripts
[root@webserver cgi-bin]# ls -lZ /var/web_scripts/
-rwxr-xr--. root root unconfined_u:object_r:var_t:s0   /var/web_scripts/

To start with we need to fix the ownership:

[root@webserver cgi-bin]# chown -R apache:apache /var/web_scripts/

Next the script needs to be executable:

chmod ug+x /var/web_scripts/

Next we need to fix the SELinux type security attribute setting, first find an exmaple of what the setting should be:

[root@webserver cgi-bin]# ll -lZ /var/www/ | grep cgi
drwxr-xr-x. root root system_u:object_r:httpd_sys_script_exec_t:s0 cgi-bin

Now we create and apply the SELinux policy:

[root@webserver cgi-bin]# semanage fcontext -at httpd_sys_script_exec_t "/var/web_scripts(/.*)?"
[root@webserver cgi-bin]# restorecon -Rv /var/web_scripts
restorecon reset /var/web_scripts context unconfined_u:object_r:var_t:s0->unconfined_u:object_r:httpd_sys_script_exec_t:s0
restorecon reset /var/web_scripts/ context unconfined_u:object_r:var_t:s0->unconfined_u:object_r:httpd_sys_script_exec_t:s0

Then confirm that this has worked:

[root@webserver cgi-bin]# ls -lZ /var/ | grep web_scripts
drwxr-xr-x. apache apache unconfined_u:object_r:httpd_sys_script_exec_t:s0 web_scripts
[root@webserver cgi-bin]# ls -lZ /var/web_scripts
-rwxr-xr--. apache apache unconfined_u:object_r:httpd_sys_script_exec_t:s0

Now we make Apache aware of our new folder. We can either update the main config file, or edit/create a vhost file. We’ve already covered other examples where we used vhosts, so this time we’ll edit the main Apache config file. SO we made a duplicate of the original ScriptAlias line and commented out the original line and modified the duplicate so that we end up with:

    #ScriptAlias /cgi-bin/ "/var/www/cgi-bin/"
    ScriptAlias /cgi-bin/ "/var/web_scripts"

We also have to add in a new Directory directive to override the high level deny ‘/’ directive, so we insert the following extract:

        AllowOverride None
        Options None
        Require all granted

Then we restarted httpd:

[root@webserver web_scripts]# systemctl restart httpd

Now everything should be working, so let’s test with Firefox/Chrome/elinks/curl:

[root@webserver web_scripts]# curl http://localhost/cgi-bin/

hello world - this time we are in non-standard folder