{"id":169166,"date":"2026-06-19T08:27:42","date_gmt":"2026-06-19T05:27:42","guid":{"rendered":"https:\/\/computingforgeeks.com\/?p=169166"},"modified":"2026-06-19T08:27:42","modified_gmt":"2026-06-19T05:27:42","slug":"install-nginx-opensuse-leap","status":"publish","type":"post","link":"https:\/\/computingforgeeks.com\/install-nginx-opensuse-leap\/","title":{"rendered":"Install Nginx Web Server on openSUSE Leap 16"},"content":{"rendered":"<p>A web server is only useful once three pieces line up: the nginx package, the firewall that lets traffic reach it, and the document root it serves. On openSUSE Leap 16 those pieces sit in slightly different places than they do on a Debian or RHEL box, and Leap 16 ships SELinux in enforcing mode for the first time, so a couple of steps behave in ways that catch people out. This guide walks through how to install nginx on openSUSE Leap 16, open the firewall, serve a real page, and add a name-based virtual host, with every command run on a live machine.<\/p>\n\n<p>You will set up the nginx package from the official <code>repo-oss<\/code> repository, point firewalld at it, serve content from the SUSE document root, and host more than one site from a single server using <code>vhosts.d<\/code>. We also test how the new enforcing SELinux policy treats nginx, because it does not behave the way the RHEL family does.<\/p>\n\n<p><em>Confirmed working on openSUSE Leap 16.0 (nginx 1.27.2) in June 2026.<\/em><\/p>\n\n<h2>Prerequisites<\/h2>\n\n<p>nginx itself is light. What you size for is the traffic and the content it fronts. A static site or a reverse proxy in front of an app is happy on 1 vCPU and 1 GB of RAM; the moment nginx caches responses or terminates TLS at volume, RAM and CPU follow connection count and the size of your cache, not the nginx binary. For a real public site, start at 2 vCPU and 2 GB and watch <code>worker_connections<\/code> and open file descriptors as the levers, then grow from there.<\/p>\n\n<ul>\n<li>A running openSUSE Leap 16 server with sudo access. The lab box here used 2 vCPU and 2 GB of RAM, which is a floor for following along, not a production recommendation.<\/li>\n<li>A non-root user in the <code>wheel<\/code> group. If you have not done the base setup yet, work through the <a href=\"https:\/\/computingforgeeks.com\/opensuse-leap-initial-server-setup\/\">initial server setup and hardening<\/a> first.<\/li>\n<li>Network access to the openSUSE CDN so <code>zypper<\/code> can reach <code>repo-oss<\/code>.<\/li>\n<\/ul>\n\n<h2>Install nginx from the OSS repository<\/h2>\n\n<p>nginx lives in the default <code>repo-oss<\/code> repository, so there is no third-party repo to add. Refresh the metadata and install the package:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>sudo zypper refresh\nsudo zypper install nginx<\/code><\/pre>\n\n\n<p>The package pulls in a small set of Lua and module dependencies and creates a dedicated <code>nginx<\/code> system user. Once it finishes, confirm the version. Here is the first Leap 16 quirk: the <code>nginx<\/code> binary lives in <code>\/usr\/sbin<\/code>, which is not on a normal user&#8217;s <code>PATH<\/code>, so call it through sudo or by full path:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>sudo nginx -v<\/code><\/pre>\n\n\n<p>The version prints on stderr:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>nginx version: nginx\/1.27.2<\/code><\/pre>\n\n\n<h2>Start nginx and enable it at boot<\/h2>\n\n<p>The package does not start the service for you. Enable and start it in one step so it survives a reboot:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>sudo systemctl enable --now nginx<\/code><\/pre>\n\n\n<p>Check that systemd reports it active:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>systemctl status nginx --no-pager<\/code><\/pre>\n\n\n<p>The status line should read <code>active (running)<\/code>, and you will see that systemd runs an <code>nginx -t<\/code> config test before every start:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>\u25cf nginx.service - The nginx HTTP and reverse proxy server\n     Loaded: loaded (\/usr\/lib\/systemd\/system\/nginx.service; enabled; preset: disabled)\n     Active: active (running) since Fri 2026-06-19 01:01:58 CEST; 13ms ago\n    Process: 3875 ExecStartPre=\/usr\/sbin\/nginx -t (code=exited, status=0\/SUCCESS)\n   Main PID: 3878 ((nginx))<\/code><\/pre>\n\n\n<h2>Open the firewall<\/h2>\n\n<p>Leap 16 runs firewalld, and the default zone blocks inbound HTTP and HTTPS. Add both services and reload so nginx is reachable from the network:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>sudo firewall-cmd --permanent --add-service=http\nsudo firewall-cmd --permanent --add-service=https\nsudo firewall-cmd --reload<\/code><\/pre>\n\n\n<p>Confirm both services are now allowed in the active zone:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>sudo firewall-cmd --list-services<\/code><\/pre>\n\n\n<p>You should see <code>http<\/code> and <code>https<\/code> alongside <code>ssh<\/code> and <code>cockpit<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>cockpit dhcpv6-client http https ssh<\/code><\/pre>\n\n\n<h2>Serve the first page from the SUSE document root<\/h2>\n\n<p>This is where openSUSE differs from almost every other distro. The default document root is <strong><code>\/srv\/www\/htdocs\/<\/code><\/strong>, not <code>\/var\/www\/html<\/code> or <code>\/usr\/share\/nginx\/html<\/code>. By default that directory only holds a <code>50x.html<\/code> error page, so a fresh install answers with a blunt <code>403 Forbidden<\/code> until you give it an index file:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>curl -I http:\/\/localhost<\/code><\/pre>\n\n\n<p>Before you add content, that request returns:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>HTTP\/1.1 403 Forbidden\nServer: nginx\/1.27.2<\/code><\/pre>\n\n\n<p>Drop an index page into the document root and the 403 turns into a 200:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>echo '&lt;h1&gt;nginx is running on openSUSE Leap 16&lt;\/h1&gt;' | sudo tee \/srv\/www\/htdocs\/index.html<\/code><\/pre>\n\n\n<p>Request it again and nginx serves the file:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>curl -I http:\/\/localhost<\/code><\/pre>\n\n\n<p>The status is now <code>200 OK<\/code>. Point a browser at the server&#8217;s IP address and you get the page back.<\/p>\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1280\" height=\"720\" src=\"https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-nginx-welcome-page-opensuse-leap.png\" alt=\"nginx web server welcome page in browser on openSUSE Leap 16\" class=\"wp-image-169153\" title=\"\" srcset=\"https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-nginx-welcome-page-opensuse-leap.png 1280w, https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-nginx-welcome-page-opensuse-leap-300x169.png 300w, https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-nginx-welcome-page-opensuse-leap-1024x576.png 1024w, https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-nginx-welcome-page-opensuse-leap-768x432.png 768w\" sizes=\"auto, (max-width: 1280px) 100vw, 1280px\" \/><\/figure>\n\n\n<h2>Host multiple sites with name-based virtual hosts<\/h2>\n\n<p>nginx on Leap 16 reads extra server blocks from <code>\/etc\/nginx\/vhosts.d\/<\/code>. The main <code>nginx.conf<\/code> already pulls them in with <code>include vhosts.d\/*.conf;<\/code>, so you never edit the shipped config to add a site. Each site gets its own file.<\/p>\n\n<p>Because the same hostname and web root repeat across several commands, set them once as shell variables and reuse them. Change the two values to match your real domain:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>export SITE_DOMAIN=\"example.com\"\nexport WEB_ROOT=\"\/srv\/www\/vhosts\/${SITE_DOMAIN}\"<\/code><\/pre>\n\n\n<p>Create the document root for the site and give it a landing page:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>sudo mkdir -p \"${WEB_ROOT}\"\necho \"&lt;h1&gt;${SITE_DOMAIN} served by nginx on Leap 16&lt;\/h1&gt;\" | sudo tee \"${WEB_ROOT}\/index.html\"<\/code><\/pre>\n\n\n<p>Now write the server block. Open a new file under <code>vhosts.d<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>sudo vim \/etc\/nginx\/vhosts.d\/example.com.conf<\/code><\/pre>\n\n\n<p>Add the following, keeping the paths in line with the variables you set above:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>server {\n    listen 80;\n    server_name example.com www.example.com;\n    root \/srv\/www\/vhosts\/example.com;\n    index index.html;\n\n    access_log \/var\/log\/nginx\/example.com-access.log;\n    error_log  \/var\/log\/nginx\/example.com-error.log;\n}<\/code><\/pre>\n\n\n<p>Test the configuration before reloading. <code>nginx -t<\/code> catches a stray semicolon or a bad path before it can take the service down:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>sudo nginx -t<\/code><\/pre>\n\n\n<p>A clean config reports both lines as successful:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>nginx: the configuration file \/etc\/nginx\/nginx.conf syntax is ok\nnginx: configuration file \/etc\/nginx\/nginx.conf test is successful<\/code><\/pre>\n\n\n<p>Reload to apply the new site without dropping existing connections:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>sudo systemctl reload nginx<\/code><\/pre>\n\n\n<p>You can prove the virtual host answers for its name by sending a matching <code>Host<\/code> header, which is what a browser does when it resolves the domain:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>curl -s -H \"Host: ${SITE_DOMAIN}\" http:\/\/127.0.0.1\/<\/code><\/pre>\n\n\n<p>nginx returns the virtual host&#8217;s page rather than the default document root:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>&lt;h1&gt;example.com served by nginx on Leap 16&lt;\/h1&gt;<\/code><\/pre>\n\n\n<h2>How SELinux treats nginx on Leap 16<\/h2>\n\n<p>Leap 16 is the first openSUSE release to ship SELinux in enforcing mode by default, so it is worth knowing exactly how strict it is with a web server. Check the process domain and you will see nginx runs confined as <code>httpd_t<\/code>, the same domain the RHEL family uses:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>ps -eZ | grep nginx<\/code><\/pre>\n\n\n<p>Each worker carries the confined label:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>system_u:system_r:httpd_t:s0       3878 ?        00:00:00 nginx<\/code><\/pre>\n\n\n<p>Here is the part that surprises anyone coming from Rocky or RHEL. We placed a document root outside the standard tree, at <code>\/opt\/mysite<\/code> with the generic <code>usr_t<\/code> label, pointed a server block at it, and requested it. On RHEL that earns an instant AVC denial. On this Leap 16 install the request returned <code>200 OK<\/code> with no denial in the audit log. The reason becomes clear when you list the tunable SELinux booleans:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>getsebool -a<\/code><\/pre>\n\n\n<p>On this release that command returns an empty list. There are none of the <code>httpd_*<\/code> toggles you would flip on RHEL, because the targeted policy shipped with Leap 16 does not constrain the web server&#8217;s file access the way the RHEL policy does. In practice that means you will not fight SELinux over document-root labels here, but treat it as how this release behaves rather than a permanent guarantee. Keep document roots under <code>\/srv\/www<\/code> as the clean habit, and if a future policy update tightens things, relabel a custom path the standard way:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>sudo semanage fcontext -a -t httpd_sys_content_t \"\/opt\/mysite(\/.*)?\"\nsudo restorecon -Rv \/opt\/mysite<\/code><\/pre>\n\n\n<p>That keeps your layout portable if you ever move the site to a stricter distro.<\/p>\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"940\" height=\"520\" src=\"https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-nginx-version-status-opensuse-leap.png\" alt=\"nginx version and systemctl status on openSUSE Leap 16 terminal\" class=\"wp-image-169152\" title=\"\" srcset=\"https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-nginx-version-status-opensuse-leap.png 940w, https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-nginx-version-status-opensuse-leap-300x166.png 300w, https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-nginx-version-status-opensuse-leap-768x425.png 768w\" sizes=\"auto, (max-width: 940px) 100vw, 940px\" \/><\/figure>\n\n\n<h2>Troubleshooting the first run<\/h2>\n\n<p>Three errors account for almost every failed first start on Leap 16, and all three have quick fixes.<\/p>\n\n<h3>nginx: command not found<\/h3>\n\n<p>Running <code>nginx -v<\/code> as your normal user fails because the binary is in <code>\/usr\/sbin<\/code>. Call it with <code>sudo nginx -v<\/code> or the full path <code>\/usr\/sbin\/nginx -v<\/code>. The service itself is unaffected; this only bites interactive commands.<\/p>\n\n<h3>403 Forbidden on a brand new install<\/h3>\n\n<p>A fresh document root has no <code>index.html<\/code>, so nginx has nothing to return and answers <code>403 Forbidden<\/code>. Add an index file to <code>\/srv\/www\/htdocs\/<\/code> (or your virtual host root) and the error clears. This is expected behavior, not a broken install.<\/p>\n\n<h3>The site loads but ignores your server block<\/h3>\n\n<p>If a virtual host serves the default page instead of its own, the usual cause is that <code>systemctl reload nginx<\/code> was skipped after editing the file, or <code>server_name<\/code> does not match the host you requested. Run <code>sudo nginx -t<\/code> to confirm the file parsed, reload, and verify with the <code>curl -H \"Host: ${SITE_DOMAIN}\"<\/code> check above. With the config valid and reloaded, nginx routes the request to the right block end to end.<\/p>\n\n<p>From here nginx is ready to front real applications. The natural next step is to <a href=\"https:\/\/computingforgeeks.com\/opensuse-leap-lets-encrypt-ssl\/\">put it behind a Let&#8217;s Encrypt certificate<\/a> so it serves HTTPS, or to place it in front of a container workload from the <a href=\"https:\/\/computingforgeeks.com\/install-docker-podman-opensuse-leap\/\">Docker and Podman setup<\/a>, or to manage the service and watch its logs from the browser using <a href=\"https:\/\/computingforgeeks.com\/cockpit-web-console-opensuse-leap\/\">Cockpit, the YaST replacement<\/a>. If you are still settling into the distro, the <a href=\"https:\/\/computingforgeeks.com\/things-to-do-after-installing-opensuse-leap\/\">things to do after installing Leap 16<\/a> guide and the <a href=\"https:\/\/computingforgeeks.com\/zypper-command-cheat-sheet\/\">zypper command reference<\/a> cover the groundwork around this setup.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>A web server is only useful once three pieces line up: the nginx package, the firewall that lets traffic reach it, and the document root it serves. On openSUSE Leap 16 those pieces sit in slightly different places than they do on a Debian or RHEL box, and Leap 16 ships SELinux in enforcing mode &#8230; <a title=\"Install Nginx Web Server on openSUSE Leap 16\" class=\"read-more\" href=\"https:\/\/computingforgeeks.com\/install-nginx-opensuse-leap\/\" aria-label=\"Read more about Install Nginx Web Server on openSUSE Leap 16\">Read more<\/a><\/p>\n","protected":false},"author":9,"featured_media":169165,"comment_status":"open","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[299,47,50,349],"tags":[282,177,9986,21787],"cfg_series":[39887],"class_list":["post-169166","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-how-to","category-linux","category-linux-tutorials","category-web-hosting","tag-linux","tag-nginx","tag-opensuse","tag-web-server","cfg_series-opensuse-leap-16"],"_links":{"self":[{"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/posts\/169166","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/users\/9"}],"replies":[{"embeddable":true,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/comments?post=169166"}],"version-history":[{"count":1,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/posts\/169166\/revisions"}],"predecessor-version":[{"id":169220,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/posts\/169166\/revisions\/169220"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/media\/169165"}],"wp:attachment":[{"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/media?parent=169166"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/categories?post=169166"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/tags?post=169166"},{"taxonomy":"cfg_series","embeddable":true,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/cfg_series?post=169166"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}