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
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:
- your Apache source tree starts at
- your Apache ServerRoot is
- your Apache DocumentRoot is
- the username under which Apache runs (the value of the
Userdirective in your
All of the
cd and other shell commands in this article that refer to directories use these locations.
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 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.
suexecmust 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
suexecmust be a valid user; that is, it must be listed in the
/etc/passwdfile. 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
suexecin 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
--suexec-gidminoptions to the
configurescript, 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
~usernamerequest, the script directory must be under the directory specified by
DOC_ROOT(defined by the
- The permissions on the specified script and its parent directory must not allow write access to either the
- 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.
suexecmust 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.
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 “$HTTPD_ROOT
/sbin/suexec“; the default value of
HTTPD_ROOT is platform-specific:
|Platform||Default value of
You may change the values of either–or both–of the
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 ‘
% /usr/local/web/apache/bin/httpd -l Compiled-in modules: http_core.c mod_so.c suexec: enabled; valid wrapper /usr/local/web/apache/bin/suexec
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.
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
configurescript that you want the wrapper to be built as well. Without this option,
suexecwill not be built, even if there are other
suexecoptions on the command line.
- This must be the username under which your Apache server runs; that is, the one specified on the
Userdirective outside all
suexecis 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
- 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
.cgifiles) 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’tunder the DocumentRoot.The default value for this option is PREFIX
/share/htdocs, where ‘PREFIX’ comes from the value of the
--prefixoption, 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
suexectakes 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
- 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
- Not only is the list of environment variables examined and sanitized before the script is invoked, but the default
PATHis 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
- As with the
--suexec-gidminoption described earlier, this option is used to inform
suexecof 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
- 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
suexecwrapper will inherit the umask setting from the parent Apache server process.
- This option specifies the subdirectory underneath a user’s home directory that
suexecwill use to find scripts for
~username-style URLs. This needs to match the setting of the
UserDirdirective in your server configuration files.Note:
suexeccan only handle simple subdirectory expressions. The more complex pattern-handling capabilities of the
mod_userdirmodule (which implements the
UserDirdirective) cannot be used with the
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.
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
Groupdirectives in the server configuration file,
The username to use is determined by checking these in the above order.
Group directives are ordinarily ignored inside
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-
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 ‘
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
suexecby default, which may cause you problems. If you don’t want it, you’ll need to either rebuild Apache or disable the
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
- Clear the
- Change the owner to be someone other than
- Delete or rename it
and then restart the Apache server. Doing any one of these will render the
suexecfacility 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
suexecis enabled, you haven’t done enough yet.
The simplest way to verify that
suexecis 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 #!/bin/sh echo "Content-type: text/plain" echo "" echo "Username="`whoami` EOS # 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
Note that the
cgi-bin/directory isn’t under the DocumentRoot, which is why the
--suexec-docrootvalue 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
suexeccan be invoked, you should test both of them:
- First, create a
container (or use an existing one) in your server configuration files, and add
Groupdirectives to it. Pick some username and group that are different from the normal server user. Next, make sure that you have a
ScriptAliasdirective 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
. If you get an error, examine the server error log and the
- User directory
- To test that
suexecwill properly handle a CGI script in a user’s directory, copy your
showuser.cgiscript 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
user/showuser.cgi>. If you get an error page, look at the Apache and
suexecproblem 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
suexecproblem 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
suexeclog (which is located at
/usr/local/web/apache/logs/suexec_log, according to the assumptions section of this article). The
suexecerror 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
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.
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
- CGI scripts in user directories (as specified by the
USERDIR_SUFFIXdefinition, set by the
--suexec-userdiroption) will be executed as the owning user if and only if
- the script was requested using the
- all of the ownership and permission requirements are met
~usernameURL 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-
- the script was requested using the
- CGI scripts in all user directories accessed through
~usernameURLs will go through the
suexecprocess–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
Another aspect of the use of
suexecis that, if you have virtual hosts with different
Groupvalues, 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
suexecis 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
suexecwrapper 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:
- Q: The single
--suexec-docrootvalue is irksome. I have 50 virtual hosts with
/vhost2, and so on. The only way I can get
suexecto work with these is to use
--suexec-docroot=/, which hardly seems secure.
- A: This is unfortunately the way it is with the
suexecthat comes with Apache up through version 1.3.11. The value you specify for
--suexec-docrootmust be an ancestor of all of the non-
~usernamedocuments 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
- Q: I only want
suexecto be used in certain directories or user accounts.
- A: As of Apache 1.3.11,
suexecis 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.
- Q: Why don’t the Apache CGI error messages say there’s a problem with
- A: 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.
- Q: Why aren’t
suexec‘s error messages logged in the Apache server log?
- A: In order for the messages from
suexecto 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.
- There are a few articles on the Web about working with the
suexecwrapper. Don’t neglect the
manpage 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:
http://www.apache.org/docs/suexec_1_2.html> (this is largely obsolete)
suexecapplication 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.
If you need to build Apache from source in order to add or change the
suexecparameters, 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: http_core.c mod_so.c suexec: enabled; valid wrapper /usr/local/web/apache/bin/suexec
- Clear the