Setting up a basic web server

Peter Boy (pboy), Emmanuel Seyman (eseyman) Version F41,F42 Last review: 2025-09-05
The recommended and fully supported Fedora Web server is Apache, named httpd in the distribution. It is a Fedora Server key functionality that is part of the services specified in the technical specification. This article is about setting up a basic server. Further articles build on this and describe additional deployment configurations.

You are in the Fedora Server documentation staging area!

These documents are not approved yet and may be incomplete and/or incorrect. Take everything here with a grain of salt! You would probably prefer to study the published documentation.

Status of this document: Awaiting review (Jan 16, 2025).

The Fedora Web server httpd is an Apache web server. This document covers in detail the setup and maintenance of a basic Web service serving directly some Web pages. A typical use scenario would be the Jamstack concept or a typical "one pager" more or less static web presentation (not to be confused with one page microservice application).

Additional articles describe how to use and further extend the configuration for other more complex usage scenarios, such as a frontend for application servers, a proxy for containers, the integration of dynamic languages, and other deployment options.

How it works

A Fedora Web Server installation stores the configuration in subdirectories of /etc/httpd. The directory /etc/httpd/conf.d is the more important one and stores especially the detailed and customized configuration options and supplements the generic configuration in /etc/httpd/conf/httpd.conf. The directory /etc/httpd/conf.modules.d contains the modules to be loaded dynamically and their configuration. Only very rarely something needs a change here.

original version

The default data store is /var/www with the html-files in its subdirectory html. Additional directories are provided for extended configurations, e.g. the cgi-bin subdirectory for storing (classic) CGI files separately from document root.

This structure dates back to the early days of Linux systems, when the server hardware was capable to serve only one site (i.e. one Domain). As the hardware got more powerful, and became capable of serving more than one domain, "virtual hosts" were added that provided additional domains – distinguished either by name (virtual named hosts) or by IP. This situation is still virulent in the term "main“ site or "main“ server.

All these distinctions have now been abandoned. Today, it is widespread for a server to host several domains. And it is now best practice to configure everything as a virtual host, even if a server only serves one domain.

Following this evolution you need domain-specific subdirectories and therein additional appropriate subdirectories replacing the current outdated /var/www/[html|cgi-bin] structure of a Fedora default installation.

Additionally, in todays web world you would have many domains, which are served by one or more interdependant applications. A Web server is just one element of the software mix. According to the FHS, the /srv directory is the appropriate place for storing data. You create a domain-specific directory, e.g. example.com, and therein a htdocs subdirectory for the Web server, a webapps subdirectory for your web applicaion, e.g Ruby on Rails or Wildfly, a mail subdirectory for a postfix/dovecot mail hub, etc.

And there is yet another structural change. Every website now uses SSL/https as standard. It is no longer an add-on to the a standard http protocol, but an integral part of every website. Almost all browsers issue a serious warning when they come across an http page. This development is also not well supported by the current Fedora default installation.

In this guide, we will supplement and adjust the current standard httpd installation to take into account the outlined evolution and to create a solid foundation for a runtime environment that works as error-free as possible and makes the system administrator’s work easier. We are introducing additional subdirectories and changes to the default configuration files. Additionally, we are providing template files to make it easier to configure virtual hosts.

shorter alternative

The Fedora default configuration leaves the system administrator with a lot of work to do in order to set up the web server for multiple websites encrypted with SSL using the various architectures in use today.

This guide is intended to assist and relieve the administrator. It introduces additional directory structures that support virtual hosts, which are the basic principle today. It supports the use of the /srv directory for locale and extensive web applications and removes some internal dependencies which may in some circumstances result in operation failures. Additionally, it provides boilerplate templates to facilitate the creation of configuration files and certificates. The system administrator no longer needs to write all this themselves and 'reinvent the wheel' every time and again.

unmodified coda

We describe a manual installation process and, as an alternative, an Ansible playbook to achieve the same result automatically.

Manual Installation

  1. Prepare Storage

    Set up one or more of the following alternatives.

    1. Create a logical volume for /var/www either in the root volume group or in the user data volume group, depending on your system installation setup. We assume a volume of 5 GiB in the user data volume group here. Use either Cockpit or CLI

      […]$ sudo lvcreate -L 5G -n htdocs fedora_usrvg
      […]$ sudo mkfs.xfs /dev/fedora_usrvg/htdocs
      […]$ sudo mkdir /var/www
      […]$ sudo echo "UUID=$(blkid -s UUID -o value /dev/fedora_usrvg/htdocs) /var/www              auto    defaults    0 0"  >> /etc/fstab
      […]$ sudo mount -a
      […]$ sudo df -h
      Filesystem                        Size  Used Avail Use% Mounted on
      /dev/mapper/fedora_sysvg-root      12G  3.3G  8.0G  29% /
      ...
      /dev/mapper/fedora_usrvg-htdocs   5.0G  130M  4.9G   3% /var/www
    2. For the /var/www you may also decide for a thinly provisioned setup, instead. Create a thinly provisioned logical volume ˚htdocs˚ in a user data pool. Specify an initial maximum size based on current planning (can be increased later if necessary). Use either Cockpit or CLI

      […]# lvcreate --virtualsize 5G --thin fedora_usrvg/thinpool -n htdocs
        Logical volume "htdocs" created.
      […]# lvs
      LV       VG           Attr       LSize    Pool     . . .
      root     fedora_sysvg -wi-ao----  <11.18g
      . . .
      thinpool fedora_usrvg twi-aotz--  278.85g
      htdocs   fedora_usrvg Vwi-a-tz--    5.00g thinpool  . . .
      
      […]# mkfs.xfs /dev/fedora_usrvg/htdocs
      meta-data=/dev/fedora_usrvg/htdocs isize=512    agcount=8, agsize=163840 blks
               =                       sectsz=512   attr=2, projid32bit=1
      ...
               =                       sectsz=512   sunit=16 blks, lazy-count=1
      realtime =none                   extsz=4096   blocks=0, rtextents=0
      Discarding blocks...Done.
      
      […]# mkdir /var/www
      […]# echo "UUID=$(blkid -s UUID -o value /dev/fedora_usrvg/webconf) /var/www              auto    defaults    0 0"  >> /etc/fstab
      […]# mount -a
      […]# df -h
      Filesystem                        Size  Used Avail Use% Mounted on
      /dev/mapper/fedora_sysvg-root      12G  3.3G  8.0G  29% /
      ...
      /dev/mapper/fedora_usrvg-htdocs   5.0G  130M  4.9G   3% /var/www
    3. If not already done, create a logical volume for /srv either in the root volume group or in the user data volume group, depending on your system installation setup. We assume a volume of 50 GiB in the user data volume group here.

      […]$ sudo lvcreate -L 5G -n srv fedora_usrvg
      […]$ sudo mkfs.xfs -L srv /dev/mapper/fedora_usrvg-pgsql
      […]$ sudo echo "UUID=$(blkid -s UUID -o value /dev/mapper/fedora_usrvg-srv) /srv            xfs     defaults        0 0"  >> /etc/fstab
      […]$ sudo mount -a
      […]$ sudo df -h
      Filesystem                        Size  Used Avail Use% Mounted on
      /dev/mapper/fedora_sysvg-root    12G  3.3G  7.9G  30% /
      ...
      /dev/mapper/fedora_usrvg-srv     50G  ...  ...   ...  /srv
  2. Install the httpd web server

    […]$ sudo dnf install httpd mod_ssl mod_md
    […]$ sudo firewall-cmd --add-service=https --permanent
    […]$ sudo firewall-cmd --add-service=http  --permanent
    […]$ sudo firewall-cmd --reload
  3. Start the web server and check the status

    […]$ sudo systemctl start httpd
    […]$ sudo systemctl status httpd

    The Web server should already answer to requests. Enter your server’s address into your browser’s address input field and Fedora test page should come up.

    httpd basic setup 030
    Figure 1. Fedora test page

    This intermediate step is important as the server creates the default self-signed certificates.

    If everything works as expected, activate the automatic server start at boot up.

    […]$ sudo systemctl enable httpd
    Created symlink /etc/systemd/system/multi-user.target.wants/httpd.service → /usr/lib/systemd/system/httpd.service.
  4. Update basic configuration

    1. Replace the main config file

      […]$ sudo mv /etc/httpd/conf/httpd.conf{,.f42}
      […]$ sudo wget https://hera.resdigita.eu/webservice-doc/conf/httpd.conf -P /etc/httpd/conf/
    2. Add a local customize.conf file

    3. Create a new subdirectory for vhost configuration

      […]$ sudo mkdir  /etc/httpd/conf.vhosts.d
    4. Integrate the directory into httpd configuration

      […]$ sudo wget https://hera.resdigita.eu/webservice-doc/conf.d/vhosts.conf  -P /etc/httpd/conf.d/
  5. Update the ssl configuration

    Download and replace the configuration file

    […]$ sudo mv /etc/httpd/conf.d/ssl.conf{,.f42}
    […]$ sudo wget https://hera.resdigita.eu/webservice-doc/conf.d/ssl.conf  -P /etc/httpd/conf.d
  6. Customize the basic configuration.

    In any case you may specify a server name to avoid warning message at startup

    […]$ sudo vim /etc/httpd/conf.d/customize.conf
    ...
    # Change this to Listen on a specific IP address, but note that if
    # httpd.service is enabled to run at boot time, the address may not be
    # available when the service starts.  See the httpd.service(8) man
    # page for more information.
    #
    #Listen 12.34.56.78:80
    Listen 80                          # <== Modify only if specifically advised!
    
    # ServerName gives the name and port that the server uses to identify itself.
    # This can often be determined automatically, but we recommend you specify
    # it explicitly to prevent problems during startup.
    #
    # If your host doesn't have a registered DNS name, enter its IP address here.
    #
    #ServerName www.example.com:80
    ServerName myweb.mydomain.tld      # <== Add the default server name
  7. Create a dedicated fallback default Web page

    The objective is to ensure that one of the dedicated websites does not inadvertently become the default page.

    1. Copy the default vhost configuration file

      […]$ sudo wget https://hera.resdigita.eu/webservice-doc/conf.vhosts.d/aa_default.vhost  -P /etc/httpd/conf.vhosts.d/
    2. Create the root directory for the default host. Adjust the parent directory part!

      […]$ sudo mkdir -p /[var/www|srv]/aa_default/htdocs

      Leave the directory empty to trigger the default Fedora index page, if you want just a fallback default page.

    3. Optional: Provide a customized index page

      If you want to publish customized content instead of triggering the default Fedora test page, you have to provide a valid index.html file and a certificate.

      For quite a simple page, you may start with an example page end edit it according to your requirements. Skip the download, if you want to start from scratch.

      […]$ sudo wget https://hera.resdigita.eu/webservice-doc/htdocs/default/index.{html,css}  -P [/var/www|/srv]/aa_default/htdocs/
      
      […]$ sudo vim [/var/www|/srv]/aa_default/htdocs/index.html
  8. Optional: Provide a certificate for secure https protocol

    Edit the default vhost definition. See the comments inside the config file to adjust it. Specifically, enter your DNS hostname and Webmaster address.

    […]$ sudo vim /etc/httpd/conf.vhosts.d/aa_default.vhost
        # aa_default.vhost
        #
        # Apache vhost configuration of a generic default vhost.
        # It needs to be the first file in an alphabetical sort of all vhost
        …​
        # If you don’t set name here it is inherited from main configuration as
        # set in ~/conf.d/customize.conf. If it is not set there either, Apache
        # tries to determine ServerName by reverse DNS request.
        ServerName      ${MY_DOMAIN.TLD}
        #ServerAlias    ${OPTIONAL_ALIAS}
    
        #=⇒ Adjust the mail address as appropriate!
        ServerAdmin     ${WEBMASTER@MY_DOMAIN.TLD}

    Follow step 6 in the chapter "Manually setting up a basic web site"

  9. Optional: Add template(s) for vhost definitions

    We are going to provide some template files containing boilerplate code for vairous types of web pages.

  10. Restart and check the httpd server

    […]$ sudo systemctl restart  httpd
    […]$ sudo httpd -S

Ansible assisted installation

coming soon (hopefully)

Manually setting up a basic web site

  1. Ensure the necessary Domain Name entries are available

    […]$ sudo resolvectl query YOUR_DOMAIN_NAME(S)
  2. Setup the web site Document Root directory

    Create a subdirectory with an appropriate name in the specified directory position.It makes sense to follow a strict naming convention. We always use a short name here consisting of the domain name without the TLD part, i.e. “MY_DOMAIN” in the case of “MY_DOMA’IN.ORG”..

    […]$ sudo mkdir -p  /[var/www|srv]/SITE_SHORT_NAME/htdocs

    If you use the /srv direcotry as a base, set the correct SELinux labels for htdocs.

    […]# semanage fcontext -a -t httpd_sys_content_t  -s system_u  "/srv/SITE_SHORT_NAME/htdocs(/.*)?"
    […]# restorecon -R -vF /srv/SITE_SHORT_NAME/htdocs
    Relabeled /srv/SITE_SHORT_NAME/htdocs from unconfined_u:object_r:var_t:s0 to system_u:object_r:httpd_sys_content_t:s0

    In the case of a large-scale web application such as Redmine or Wordpress, it may be useful to create a separate, thinly provisioned logical volume for the domain instead.

    In any case, leave the document root (htdocs) empty for now.

  3. Configure a Virtual Host for the domain

    1. Copy the starter web site template

      cp /etc/http/conf.vhost.d/vhost-starter-html.template /etc/http/conf.vhost.d/[SITE_SHOFRT_NAME].vhost

      It is good practice to follow a systematic naming convention. Use the same as for the naming of the htdocs directories above.

    2. Open the configuration file and adjust the placeholders.
      Use the vim search and replace function to replace the placeholder at all the required locations in one go.

      […]$ sudo vi /etc/httpd/conf.vhost.d/[SITE_SHOFRT_NAME].vhost
      
      # vhost-starter-html.template
      ...
      
      #==> To adjust the template in vi/vim copy each line
      # and replace the second part accordingly
      # : %s/SHORT_DESCR/real_short_descr/g	e.g. my-domain.org production server
      # : %s/FQN_NAME/your_domain/g			e.g. my-domain.org
      # : %s/BASE_NAME/your_shortname/g		e.g. my-domain.org
      # : %s/OPTIONAL_ALIAS/your_alias/g		e.g. www.my-domain.org
      # afterwards delete these lines
      ...

      Then browse through the file, check the settings according to the comments.

  4. Restart and check the web server

    […]# systemctl  restart  httpd
    […]# systemctl  status  httpd
    ● httpd.service - The Apache HTTP Server
         Loaded: loaded (/usr/lib/systemd/system/httpd.service; enabled; preset: disabled)
         Active: active (running) since ...
      ...
      ...
  5. Test the configuration

    Again, enter your server’s address into your browser’s address input field. Because we already re-route everything to the secure site which uses a self-signed certificate so far, you get a warning message. Select "Advanced" and accept the 'risk' here. You’ll see the provisional test page.

  6. Provide a SSL certificate

    Use one of the alternatives as appropriate.

    1. Configure access to your commercial certificates

      Enter the storage addresses of your certificates in the appropriate place in the configuration file.

          …​
          # DEFAULT distribution provided, needed for initial startup.
          # =⇒ Comment OUT when module md created a certificate or you use custom
          # certificates.
          SSLCertificateFile      /etc/pki/tls/certs/localhost.crt       # ←- comment out
          SSLCertificateKeyFile   /etc/pki/tls/private/localhost.key     # ←- comment out
          …​
          # Add your custom certificate here
          #SSLCertificateFile      /etc/???                                # ←- edit
          #SSLCertificateKeyFile   /etc/???                                # ←- edit
          #SSLCertificateChainFile /etc/???                                # ←- edit
      
      […]$ sudo systemctl restart httpd
    2. Using a Let’s Encrypt certificate by Apache md module

      If you want to use Let’s Encrypt to maintain a certificate for the Web site only, the preferred method is using the md module. At the top of the file edit

          # LetsEncrypt certificates should be managed by Apache md module.
          # =⇒ To activate, remove the leading '#' character
          # you need to set an explizit hostname and comment out
          # the default distribution provided certificates further down.
          # =⇒ Adjust the mail address as appropriate!
          MDContactEmail ${WEBMASTER@MY_DOMAIN.TLD}        # ←- edit
          MDCertificateAgreement accepted                  # ←- edit
          MDomain ${MY_DOMAIN.TLD}                         # ←- edit
      
      […]$ sudo systemctl restart httpd

      Wait a few seconds to give the md module time to perform the necessary configuration steps in the background. Watch the ssl log file.

      […]$ sudo tail -f ssl_error_log
          …​
          [md:notice] [pid 6101:tid 6103] AH10059: The Managed Domain {MY_DOMAIN.TLD} has been setup and changes will be activated on next (graceful) server restart.
          …​

      When you see note as above, trigger a graceful restart of httpd.

      […]# apachectl  graceful

      The https communication now works. The system automatically renews the certificate, you don’t have to care about it.

    3. Use Let’s Encrypt certbot to generate a certificate

      Alternatively, or if you want to use the Let’s Encrypt certificate for other purpuses (e.g. mail or Cockpit) as well, use Certbot.

      […]$ sudo letsencrypt certonly --webroot  --webroot-path [/var/www|/srv]/aa_default/htdocs   --agree-tos --domains MY_DOMAIN.TLD[, OPTIONAL.MY_DOMAIN.TLD]   --renew-by-default   --email WEBMASTER@MY_DOMAIN.TLD

      Editing the vhost config file, comment out the self-sígned default certificates and comment in the 3 lines with the Let’s Encrypt certificates. Restart httpd.

      […]$ sudo vim /etc/httpd/conf.vhosts.d/aa_default.vhost
          # aa_default.vhost
          #
          # Apache vhost configuration of a generic default vhost.
          …​
          # DEFAULT distribution provided, needed for initial startup.
          #=⇒ Comment OUT when module md created a certificate or you use custom
          # certificates.
          SSLCertificateFile      /etc/pki/tls/certs/localhost.crt               # ←- comment out
          SSLCertificateKeyFile   /etc/pki/tls/private/localhost.key             # ←- comment out
      
          # LetsEncrypt certificates managed by certbot (NOT by module md!)
          SSLCertificateFile      /etc/letsencrypt/live/DOMAIN_NAME/cert.pem      # ←- comment in
          SSLCertificateKeyFile   /etc/letsencrypt/live/DOMAIN_NAME/privkey.pem   # ←- comment in
          SSLCertificateChainFile /etc/letsencrypt/live/DOMAIN_NAME/chain.pem     # ←- comment in
      
      […]$ sudo systemctl restart httpd

      Sometimes none of the described Let’s Encrypt generation methods work on aarch64 architecture. The Let’s Encrypt server does have permission to access your site. The issue is yet under investigation.

      As a temporary workaround, stop the httpd server and use the standalone method to generate the certificates.

      […]$ sudo letsencrypt  certonly --standalone --preferred-challenges http -d MY_DOMAIN.TLD  --email WEBMASTER@MY_DOMAIN.TLD  --agree-tos

      Modify the vhost file as described and restart the httpd server.

  7. Final commissioning

    Copy your content into the htdocs subdirectory, add helper subdirectories as auth.d oder cgi-bin to the domain directory as appropriate.

Done.

Ansible assisted setting up a simple web site

coming soon (hopefully)