Suexec and Apache: A Tutorial

by Ken Coar

When you're running an Apache Web server for yourself, you don't think about the user that's running the Apache server itself (typically nobody on Linux systems). But what if you're an ISP with multiple companies being hosted on your system? Or an educational institution with faculty who want to be able to execute their own scripts?

The Apache Web server, like most if not all of the others in common use today, lets you execute arbitrarily complex operations through the use of CGI scripts. These can involve database lookups, system administration functions, real-time control of machinery, online payments, or almost anything else you can think of.

Ordinarily, all of these things occur in the context of the user running the Apache server itself (typically nobody on Linux systems). This is fine when you're using a system that is owned and used by a single entity...but what if you're an ISP with multiple companies being hosted on your system? Or an educational institution with faculty who want to be able to execute their own scripts? Either everything has to be accessible to the Apache nobody user, or you have to run multiple instances of Apache on multiple ports and IP addresses, one of each per user, with the concomitant confusion of configuration files.

On the other hand, if the server is to be allowed to change its identity, it needs to be done in a controlled manner, so that the chance of compromising your system's security is kept to a minimum. (Remember, Apache is usually started as root and only changes to nobody later!)

The suexec (pronounced 'SUE-ex-Ek') tool helps make this possible. It's found in the src/support/ directory under your Apache source tree.

Assumptions in This Article
For the rest of this article, I'm going to make the following assumptions:

  1. your Apache source tree starts at ./apache-1.3/
  2. your Apache ServerRoot is /usr/local/web/apache
  3. your Apache DocumentRoot is /usr/local/web/htdocs
  4. the username under which Apache runs (the value of the User directive in your httpd.conf file) is nobody

All of the cd and other shell commands in this article that refer to directories use these locations.

How Does Suexec Work?

Since suexec works by wrapping an operation up in a package executed under a different username, it's called a wrapper. In order to execute a script under the auspices of the wrapper, the Apache server creates a child process running the suexec binary and passes the particulars to it. The wrapper verifies that all the security requirements are met, edits the list of environment variables so that only the ones on its 'trusted' list are available, closes its logfile, and calls some flavour of execv(2) to load the script into the edited process environment, replacing suexec itself.

Requirements For suexec Operation
Since suexec is used to run applications on your system on behalf of arbitrary people out on the Web, it's very paranoid about doing anything that might compromise your system's security. Here is a list of the conditions suexec requires to be met before it will proceed; if any don't measure up, the wrapper will log an error and not execute the script.

  • suexec must be invoked with the correct number of arguments. If it isn't, it assumes someone is trying to penetrate your system by running it outside the Apache environment.
  • The username/UID invoking suexec must be a valid user; that is, it must be listed in the /etc/passwd file. If it isn't, something's not quite right--and when in doubt, punt.
  • The username executing the wrapper must be the one that was compiled into it when it was built. Again, a mismatch here is interpreted as someone trying to use suexec in other than the prescribed way.
The requested script must be a valid Web-space reference relative to the user's directory or the DocumentRoot; it cannot be an absolute filesystem path (i.e., it cannot start with a "/") and cannot include any up-level references (i.e., no "../" references either).
  • The username and group under which the script is to be run must be valid, cannot be 'root', and must be above the minimum UID and GID values (set with the --suexecuidmin and --suexec-gidmin options to the configure script, which both default to 100). In addition, the group must be a valid name, and not just a numeric GID.
  • The wrapper must be able to change its idenity to the requested username and group.
  • The script (and obviously the directory in which it lives) must actually exist and the wrapper must be able to chdir() to the directory.
  • If the script isn't from a ~username request, the script directory must be under the directory specified by DOC_ROOT (defined by the --suexec-docroot option to configure).
  • The permissions on the specified script and its parent directory must not allow write access to either the group or the other categories.
  • The script file cannot be setuid or setgid.
  • The script and the directory must be owned by the user and group as which it is to be executed.
  • The script must be executable by the user.
  • suexec must be able to allocate memory in which to reproduce the environment variable list.
  • As you can see, the requirements for execution are pretty stringent. The sheer number of things that can go wrong argues for the use of the wrapper only when it's really necessary.

    Enabling suexec

    The suexec wrapper isn't turned on or off by any particular Apache directive setting. Instead, when the Apache server is compiled, one of the constants set (SUEXEC_BIN) is a string pointing to the location of the suexec binary. When the server starts, it looks for the binary at that location; if it's found, suexec is enabled--not otherwise. This is very important.

    This means that even a normal Apache build that was performed without any thought given to using the wrapper can suddenly become suexec-enabled if a properly protected suexec binary is put into place between server restarts. In the master sources, the default value of SUEXEC_BIN is set to "/sbin/suexec"; the default value of HTTPD_ROOT is platform-specific:

    PlatformDefault value of HTTPD_ROOTResulting default SUEXEC_BIN value
    Novell NetWaresys:/apachesys:/apache/sbin/suexec
    All others/usr/local/apache/usr/local/apache/sbin/suexec

    You may change the values of either--or both--of the HTTPD_ROOT and SUEXEC_BIN constants when you recompile the Apache server.

    If Apache does find the wrapper, it reports it in the server error log like this:

    [Thu Dec 30 01:24:43 1999] [notice] suEXEC mechanism enabled (wrapper: /usr/local/web/apache/bin/suexec)

    Up until Apache version 1.3.11, there was no way to be sure where a compiled Apache server is going to be looking for the suexec binary. As of 1.3.11, though, it's part of the 'compiled modules' report displayed by the '-l' switch:

        % /usr/local/web/apache/bin/httpd -l
        Compiled-in modules:
        suexec: enabled; valid wrapper /usr/local/web/apache/bin/suexec

    The 'enabled; valid' notation means that the wrapper is actually present in the indicated location, and the permissions are correct. If the wrapper isn't there, or the permissions are wrong, the output will indicate that suexec is disabled.

    Compiling Suexec

    Because most of suexec's control parameters are defined at compile-time, the only way to change them is to recompile. And since the wrapper works very closely with the Apache Web server--to the point of both applications having to share some compile-time definitions--the way to recompile suexec is to recompile all of Apache. If you've never done this before, you can see a brief treatment of the process in the "Building Apache at Lightspeed" section of this article.

    There are several suexec-specific options to the apache-1.3/configure script. Here they are:

    The presence of this option on the command line simply informs the configure script that you want the wrapper to be built as well. Without this option, suexec will not be built, even if there are other suexec options on the command line.

    This mustbe the username under which your Apache server runs; that is, the one specified on the User directive outside all <VirtualHost> containers. If suexec is invoked by any other user, it assumes it's some sort of probing attempt and fails to execute (after logging the user mismatch).

    The default username is www.

    This specifies the ancestor directory under which all CGI scripts need to reside in order to be acceptable to suexec. (This restriction doesn't apply to scripts activated by ~username-style URLs.) If you have multiple virtual hosts using suexec, their DocumentRoots (if you're using .cgi files) must all be located somewhere in the hierarchy under this directory, or else the wrapper will assume someone is trying to execute something unexpected and will log it as an intrusion attempt. ScriptAliased directories must be under this hierarchy as well, and this is in fact more important for them since they commonly aren't under the DocumentRoot.

    The default value for this option is PREFIX/share/htdocs, where 'PREFIX' comes from the value of the --prefix option, explicit or implied.

    Another one of suexec's restrictions is that the user it's being asked to execute the script as mustn't be considered 'privileged.' On Linux and other Unix-like systems this generally means that it mustn't be the root user, but suexec takes this a step further and will refuse to execute as any user with a group ID less than the value of this option.

    The default value for this option, if not specified, is 100.

    This specifies the name of the file to which the wrapper will report errors and successful invocations. It is opened and accessed as root, but closed before control is passed to the script.

    The default for this option is PREFIX/var/log/suexec_log, where 'PREFIX' is the value from the --prefix option.

    Not only is the list of environment variables examined and sanitized before the script is invoked, but the default PATH is set to a known list of directories as well. This list is hard-coded at compile-time, and is defined by this option.

    The default value for --suexec-safepath is /usr/local/bin:/usr/bin:/bin.

    As with the --suexec-gidmin option described earlier, this option is used to inform suexec of forbidden UID values. If a request is made that would result in the execution of a script by a user with a UID equal to or less than this value, the wrapper will log the fact and not process the request. This foils things like a request for ~root/script.

    The default value for this option is 100.

    This option defines the default permission mode to be applied to files created by the script (if it doesn't explicitly set them itself). The umask is specified as a three-digit octal number indicating which permission bits should not be set; see the description of the umask(1) command for more details.

    If this option isn't defined at compile-time, at run-time the suexec wrapper will inherit the umask setting from the parent Apache server process.

    This option specifies the subdirectory underneath a user's home directory that suexec will use to find scripts for ~username-style URLs. This needs to match the setting of the UserDir directive in your server configuration files.

    Note: suexec can only handle simple subdirectory expressions. The more complex pattern-handling capabilities of the mod_userdir module (which implements the UserDir directive) cannot be used with the suexec wrapper.

    The default --suexec-userdir setting is public_html.

    If you want to change the location of the suexec binary, you can do so by adding a new definition of SUEXEC_BIN to the compilation flags:

        % env CFLAGS="-Wall -DSUEXEC_BIN=\"/usr/local/web/apache/suexec\"" \
        >  ./configure --enable-suexec ...

    You should be extremely cautious about changing other definitions, such as HTTPD_ROOT, however, since suexec isn't the only part of Apache that uses them.

    User IDs Suexec Will Use

    Since the point of suexec is to handle certain Web requests under a different identity than the Apache server user, there needs to be some way to specify just which user. There are two places from which Apache will draw this information:

    • The username from URLs such as <URL:http://somehost.com/~username/foo.cgi>,
    • The User and Group directives in the server configuration file, httpd.conf.

    The username to use is determined by checking these in the above order.

    The User and Group directives are ordinarily ignored inside <VirtualHost> containers, but in a suexec-enabled server they take on new meaning for the virtual host, defining the identity under which CGI scripts requested through that host will be executed. If a virtual host doesn't have a User directive, it inherits the server-wide value (which defines the username under which the server itself is running) which will probably result in normal, non-suexec-enabled behaviour.

    Incorporating Suexec Into Your Apache Server
    If you have an Apache 1.3 server binary, it's capable of using a suexec wrapper if it finds one in the expected place. (Until Apache 1.3.11, there was no convenient way to find out what the 'expected place' is; as of version 1.3.11, you can find out the value of the SUEXEC_BIN compile-time constant, and whether there's a valid wrapper at that location, with the 'httpd -l' runtime switch.)

    If you're working with an Apache server that you inherited, or installed as part of a package, you might not be sure whether suexec is in place or being used. If you want to be sure about it, the best thing to do is to use the Apache build procedure, which will dot the Is and cross the Ts when you 'make install'.

    The main mechanism suexec uses to ensure safety is to rely on a bunch of settings made at compile-time. Likewise, the only way Apache can be made to even think about using suexec is it if has been compiled with that in mind. This means that you'll probably need to compile both the Apache server and suexec yourself. This is easily done as part of the normal Apache build. Just use the following command and the rest is easy:

        % cd ./apache-1.3/
        % ./configure \
        >        --enable-shared=max \
        >        --enable-module=most \
        >        --with-layout=Apache \
        >        --prefix=/usr/local/web/apache \
        >        --with-port=80 \
        >        --suexec-enable \
        >        --suexec-caller=nobody \
        >        --suexec-docroot=/usr/local/web
    The Red Hat 6.1 Apache RPM actually installs suexec by default, which may cause you problems. If you don't want it, you'll need to either rebuild Apache or disable the suexec execution.

    Disabling Suexec
    If your Apache installation is currently suexec-enabled, it's very simple to turn the wrapper off. Just do one or more of the following to the suexec binary:

    • Clear the setuid bit
    • Change the owner to be someone other than root
    • Delete or rename it

    and then restart the Apache server. Doing any one of these will render the suexec facility unusable, and Apache won't even try to involve it. To verify that your action has had the desired effect, verify (if you're running Apache 1.3.11 or later) with the "/usr/local/web/apache/bin/httpd -l" command. If the output says suexec is enabled, you haven't done enough yet.

    Testing Your Installation

    The simplest way to verify that suexec is functioning properly is to install a script that will tell you the username under which it's being invoked.

        # cd /usr/local/web/apache/cgi-bin/
        # cat > showuser.cgi << EOS
        echo "Content-type: text/plain"
        echo ""
        echo "Username="'whoami'

        # chmod 755 showuser.cgi
        # chown user1.group1 . ./showuser.cgi

    (By calling it "showuser.cgi" you can copy it directly into a user's directory without having to rename it. Filename extensions on scripts in ScriptAliased directories are ignored, so it does no harm to keep the .cgi extension.)

    Note that the cgi-bin/ directory isn't under the DocumentRoot, which is why the --suexec-docroot value was bumped up one level--that way it covers both the ServerRoot (including the cgi-bin/ directory) and the DocumentRoot.

    Since there are two ways in which suexec can be invoked, you should test both of them:

    Server-wide suexecution
    First, create a <VirtualHost> container (or use an existing one) in your server configuration files, and add User and Group directives to it. Pick some username and group that are different from the normal server user. Next, make sure that you have a ScriptAlias directive that points to the directory where you put your test script. Next, make sure that the cgi-bin/ directory and the test script are owned by the user and group you've chosen, and are mode 755. Finally, (re)start the Apache server and request the test script with some URL like <URL:http://myvirtualhost/cgi-bin/showuser.cgi>. If you get an error, examine the server error log and the suexec log.

    User directory suexecution
    To test that suexec will properly handle a CGI script in a user's directory, copy your showuser.cgi script into that user's public_html/ directory, make sure that both the script and the public_html/ directory itself are mode 755 and owned by the user, and then request the script with a URL such as <URL:http://myhost/~user/showuser.cgi>. If you get an error page, look at the Apache and suexec logs.


    Debugging a suexec problem can be frustrating, particularly since almost any problem with a CGI script in a suexec-enabled environment turns out to be related to the wrapper.

    The typical warning signal of a suexec problem is a request for a CGI script that results in a '500 Internal Server Error' page. The appropriate response behaviour to such an error is to look in the server's error log. Unfortunately, because the wrapper is applying its own restrictions and rules on the script, the server log may be quite unrevealing, containing only a single line such as the following for the failed request:

    [Sun Dec 26 20:02:55 1999] [error] [client n.n.n.n] Premature end of script headers: script

    The real error message will be found in your suexec log (which is located at /usr/local/web/apache/logs/suexec_log, according to the assumptions section of this article). The suexec error message may look like this:

        [1999-12-26 20:02:55]: uid: (user/user) gid: (group/group) cmd: test.cgi
        [1999-12-26 20:02:55]: command not in docroot (/home/user/public_html/test.cgi

    Here are a couple of other common suexec error messages:

    • directory is writable by others: (path)
    • target uid/gid (uid-1/gid-1) mismatch with directory (uid-2/gid-2) or program (uid-3/gid-3)

    If it's still not clear what's going wrong, review the list of requirements and make sure they're all being met.

    "Danger, Will Robinson!"

    When you suexec-enable your Apache Web server, a lot of behaviours change:

    • CGI scripts in ScriptAliased directories will be executed under the identity of the username specified in the User and Group directives
    • CGI scripts in user directories (as specified by the USERDIR_SUFFIX definition, set by the --suexec-userdir option) will be executed as the owning user if and only if
      1. the script was requested using the ~username syntax, and
      2. all of the ownership and permission requirements are met
      If the ~username URL format is used but the permissions/ownerships aren't correct, the result will be a '500 Internal Server Error' page, not the script being executed by the server user as in a non-suexec environment
    • CGI scripts in all user directories accessed through ~username URLs will go through the suexec process--even those that you didn't consider or expect.

    One effect of these changes is that previously-functioning user scripts may suddenly begin to fail, giving the visitor the fatal '500 Internal Server Error' page, and giving you, the Webmaster, an unrevealing "Premature end of script headers" message in the server error log. This is where it becomes easy to get frustrated by simply forgetting to check the suexec error log.

    Another aspect of the use of suexec is that, if you have virtual hosts with different User or Group values, they cannot share ScriptAliased directories--because one of the requirements is that the script and the directory must be owned by the user and group suexec is being told to use. So you may have to duplicate a lot of your cgi-bin/ stuff into per-vhost directories that are owned and protected appropriately.

    Frequently Asked Suexec Questions

    The suexec wrapper isn't perfect, and some aspects of its design result in it being less than ideally suited to all environments. Here are some of the more common questions, changes, and enhancements that come up again and again:

    The single --suexec-docroot value is irksome. I have 50 virtual hosts with DocumentRoot values like /vhost1, /vhost2, and so on. The only way I can get suexec to work with these is to use --suexec-docroot=/, which hardly seems secure.
    This is unfortunately the way it is with the suexec that comes with Apache up through version 1.3.11. The value you specify for --suexec-docroot must be an ancestor of all of the non-~username documents that use it. This restriction may be lifted in a future version, but even then it would require settings specified at compile-time, such as with something like --suexec-docroot=/vhost1,/vhost2.

    I only want suexec to be used in certain directories or user accounts.
    As of Apache 1.3.11, suexec is an all-or-nothing proposition. If it's available and enabled, it will be used in all cases when a CGI script is invoked. A future version of Apache may provide a means of controlling this with greater granularity.

    Why don't the Apache CGI error messages say there's a problem with suexec?
    Because Apache really doesn't know that for a fact. All it knows is that called an internal function to invoke the CGI, and the interaction with the script failed as described in the error message. The error might have been caused by a failure to meet suexec's requirements, or it may have been the result of a bona fide error in the script itself.

    Why aren't suexec's error messages logged in the Apache server log?
    In order for the messages from suexec to appear in the main server's log, they would have to actually be passed to Apache so that Apache did the logging. Not only is this inappropriate for the Web server to do, but there would be additional confusion about into which error log the messages should go.

    Going Further

    There are a few articles on the Web about working with the suexec wrapper. Don't neglect the man page included with the source; you can view it directly with

        % cd ./apache-1.3/src/support/
        % man ./suexec.8

    You can also find some documentation at the following URLs:

    In Conclusion
    The suexec application is a double-edged sword. It allows you to execute scripts under other personæ than the basic server user--but it can also cut you unexpectedly if you're not careful. A single misconfiguration can break all of your CGI scripts, so consider and plan carefully, and test thoroughly, before implementing the wrapper on your production systems.

    Got a Topic You Want Covered?

    If you have a particular Apache-related topic that you'd like covered in a future article in this column, please let me know; drop me an email at <coar@Apache.Org>. I do read and answer my email, usually within a few hours (although a few days may pass if I'm travelling or my mail volume is way up). If I don't respond within what seems to be a reasonable amount of time, feel free to ping me again.

    About the Author
    Ken Coar is a member of the Apache Group and a director and vice president of the Apache Software Foundation. He is also a core member of the Jikes open-source Java compiler project, a contributor to the PHP project, the author of Apache Server for Dummies, and a contributing author to Apache Server Unleashed. He can be reached via email at <coar@apache.org>.

    Appendix: Building Apache at Lightspeed

    If you need to build Apache from source in order to add or change the suexec parameters, you can use the following commands as a quick-start. You should download the latest released version of the Apache tarball and unpack it into a working directory. The top-level directory will then be ./apache-1.3, which matches assumption #1 described earlier.

        % cd ./apache-1.3
        % env CC=gcc CFLAGS="-O2 -Wall" \
        > ./configure --enable-shared=max --enable-module=most \
        >   --with-layout=Apache --prefix=/usr/local/web/apache \
        >   --with-port=80 \
        >   --enable-suexec \
        >   --suexec-caller=nobody \
        >   --suexec-docroot=/usr/local/web \
        >   --suexec-umask=022
        Configuring for Apache, Version 1.3.12-dev
         + using installation path layout: Apache (config.layout)
         + Warning: You have enabled the suEXEC feature. Be aware that you need
         + root privileges to complete the final installation step.
        Creating Makefile
        Creating Configuration.apaci in src
            [more configuration output]
        % make
            [lots of compilation output]
        % make install
            [lots more output describing file placement]
        % /usr/local/web/apache/bin/apachectl start

    If you didn't encounter any errors, you should now have a working Apache installation in the location that matches assumption #2 described earlier. It's been built to include suexec, and you should verify that this is the case:

        % /usr/local/web/apache/bin/httpd -l
        Compiled-in modules:
        suexec: enabled; valid wrapper /usr/local/web/apache/bin/suexec

    It's far beyond the scope of this article to give any more information about building Apache. If you'd like to see an article in this column about the details of building Apache, let me know.

    This article was originally published on Wednesday Jul 12th 2000
    Mobile Site | Full Site