<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ nginx - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ nginx - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Fri, 19 Jun 2026 22:33:36 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/nginx/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Self-host a Container Registry ]]>
                </title>
                <description>
                    <![CDATA[ A container registry is a storage catalog from where you can push and pull container images. There are many public and private registries available to developers such as Docker Hub, Amazon ECR, and Google Cloud Artifact Registry. But sometimes, inste... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-self-host-a-container-registry/</link>
                <guid isPermaLink="false">670ea63e203bba3017cc96ff</guid>
                
                    <category>
                        <![CDATA[ Docker ]]>
                    </category>
                
                    <category>
                        <![CDATA[ containers ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Linux ]]>
                    </category>
                
                    <category>
                        <![CDATA[ nginx ]]>
                    </category>
                
                    <category>
                        <![CDATA[ SSL ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Alex Pliutau ]]>
                </dc:creator>
                <pubDate>Tue, 15 Oct 2024 17:28:30 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1728918386211/cf6fd053-453e-4257-abcd-16942c345845.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>A container registry is a storage catalog from where you can push and pull container images.</p>
<p>There are many public and private registries available to developers such as <a target="_blank" href="https://hub.docker.com/">Docker Hub</a>, <a target="_blank" href="https://aws.amazon.com/ecr/">Amazon ECR</a>, and <a target="_blank" href="https://cloud.google.com/artifact-registry/docs">Google Cloud Artifact Registry</a>. But sometimes, instead of relying on an external vendor, you might want to host your images yourself. This gives you more control over how the registry is configured and where the container images are hosted.</p>
<p>This article is a hands-on tutorial that’ll teach you how to self-host a Container Registry.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-is-a-container-image">What is a Container Image?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-is-a-container-registry">What is a Container Registry?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-why-you-might-want-to-self-host-a-container-registry">Why you might want to self-host a Container Registry</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-self-host-a-container-registry">How to self-host a Container Registry</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-1-install-docker-and-docker-compose-on-the-server">Step 1: Install Docker and Docker Compose on the server</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-2-configure-and-run-the-registry-container">Step 2: Configure and run the registry container</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-3-run-nginx-for-handling-tls">Step 3: Run NGINX for handling TLS</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-ready-to-go">Ready to go!</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-other-options">Other options</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<p>You will get the most out of this article if you’re already familiar with the tools like Docker and NGINX, and have a general understanding of what a container is.</p>
<h2 id="heading-what-is-a-container-image">What is a Container Image?</h2>
<p>Before we talk about container registries, let's first understand what a container image is. In a nutshell, a container image is a package that includes all of the files, libraries, and configurations to run a container. They are composed of <a target="_blank" href="https://docs.docker.com/get-started/docker-concepts/building-images/understanding-image-layers/">layers</a> where each layer represents a set of file system changes that add, remove, or modify files.</p>
<p>The most common way to create a container image is to use a <strong>Dockerfile</strong>.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># build an image</span>
docker build -t pliutau/hello-world:v0 .

<span class="hljs-comment"># check the images locally</span>
docker images
<span class="hljs-comment"># REPOSITORY    TAG       IMAGE ID       CREATED          SIZE</span>
<span class="hljs-comment"># hello-world   latest    9facd12bbcdd   22 seconds ago   11MB</span>
</code></pre>
<p>This creates a container image that is stored on your local machine. But what if you want to share this image with others or use it on a different machine? This is where container registries come in.</p>
<h2 id="heading-what-is-a-container-registry">What is a Container Registry?</h2>
<p>A container registry is a storage catalog where you can push and pull container images from. The images are grouped into repositories, which are collections of related images with the same name. For example, on Docker Hub registry, <a target="_blank" href="https://hub.docker.com/_/nginx">nginx</a> is the name of the repository that contains different versions of the NGINX images.</p>
<p>Some registries are public, meaning that the images hosted on them are accessible to anyone on the Internet. Public registries such as <a target="_blank" href="https://hub.docker.com/">Docker Hub</a> are a good option to host open-source projects.</p>
<p>On the other hand, private registries provide a way to incorporate security and privacy into enterprise container image storage, either hosted in cloud or on-premises. These private registries often come with advanced security features and technical support.</p>
<p>There is a growing list of private registries available such as <a target="_blank" href="https://aws.amazon.com/ecr/">Amazon ECR</a>, <a target="_blank" href="https://cloud.google.com/artifact-registry/docs">GCP Artifact Registry</a>, <a target="_blank" href="https://github.com/features/packages">GitHub Container Registry</a>, and Docker Hub also offers a private repository feature.</p>
<p>As a developer, you interact with a container registry when using the <code>docker push</code> and <code>docker pull</code> commands.</p>
<pre><code class="lang-bash">docker push docker.io/pliutau/hello-world:v0

<span class="hljs-comment"># In case of Docker Hub we could also skip the registry part</span>
docker push pliutau/hello-world:v0
</code></pre>
<p>Let's look at the anatomy of a container image URL:</p>
<pre><code class="lang-bash">docker pull docker.io/pliutau/hello-world:v0@sha256:dc11b2...
                |            |            |          |
                ↓            ↓            ↓          ↓
             registry    repository      tag       digest
</code></pre>
<h2 id="heading-why-you-might-want-to-self-host-a-container-registry">Why You Might Want to Self-host a Container Registry</h2>
<p>Sometimes, instead of relying on a provider like AWS or GCP, you might want to host your images yourself. This keeps your infrastructure internal and makes you less reliant on external vendors. In some heavily regulated industries, this is even a requirement.</p>
<p>A self-hosted registry runs on your own servers, giving you more control over how the registry is configured and where the container images are hosted. At the same time it comes with a cost of maintaining and securing the registry.</p>
<h2 id="heading-how-to-self-host-a-container-registry">How to Self-host a Container Registry</h2>
<p>There are several open-source container registry solutions available. The most popular one is officially supported by Docker, called <a target="_blank" href="https://hub.docker.com/_/registry">registry</a>, with its implementation for storing and distributing of container images and artifacts. This means that you can run your own registry inside a container.</p>
<p>Here are the main steps to run a registry on a server:</p>
<ul>
<li><p>Install Docker and Docker Compose on the server.</p>
</li>
<li><p>Configure and run the <strong>registry</strong> container.</p>
</li>
<li><p>Run <strong>NGINX</strong> for handling TLS and forwarding requests to the registry container.</p>
</li>
<li><p>Setup SSL certificates and configure a domain.</p>
</li>
</ul>
<h3 id="heading-step-1-install-docker-and-docker-compose-on-the-server">Step 1: Install Docker and Docker Compose on the server</h3>
<p>You can use any server that supports Docker. For example, you can use a DigitalOcean Droplet with Ubuntu. For this demo I used Google Cloud Compute to create a VM with Ubuntu.</p>
<pre><code class="lang-bash">neofetch

<span class="hljs-comment"># OS: Ubuntu 20.04.6 LTS x86_64</span>
<span class="hljs-comment"># CPU: Intel Xeon (2) @ 2.200GHz</span>
<span class="hljs-comment"># Memory: 3908MiB</span>
</code></pre>
<p>Once we're inside our VM, we should install Docker and Docker Compose. Docker Compose is optional, but it makes it easier to manage multi-container applications.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># install docker engine and docker-compose</span>
sudo snap install docker

<span class="hljs-comment"># verify the installation</span>
docker --version
docker-compose --version
</code></pre>
<h3 id="heading-step-2-configure-and-run-the-registry-container">Step 2: Configure and run the registry container</h3>
<p>Next we need to configure our registry container. The following <strong>compose.yaml</strong> file will create a registry container with a volume for storing the images and a volume for storing the password file.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">services:</span>
  <span class="hljs-attr">registry:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">registry:latest</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-attr">REGISTRY_AUTH:</span> <span class="hljs-string">htpasswd</span>
      <span class="hljs-attr">REGISTRY_AUTH_HTPASSWD_REALM:</span> <span class="hljs-string">Registry</span> <span class="hljs-string">Realm</span>
      <span class="hljs-attr">REGISTRY_AUTH_HTPASSWD_PATH:</span> <span class="hljs-string">/auth/registry.password</span>
      <span class="hljs-attr">REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY:</span> <span class="hljs-string">/data</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-comment"># Mount the password file</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./registry/registry.password:/auth/registry.password</span>
      <span class="hljs-comment"># Mount the data directory</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./registry/data:/data</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-number">5000</span>
</code></pre>
<p>The password file defined in <strong>REGISTRY_AUTH_HTPASSWD_PATH</strong> is used to authenticate users when they push or pull images from the registry. We should create a password file using the <strong>htpasswd</strong> command. We should also create a folder for storing the images.</p>
<pre><code class="lang-yaml"><span class="hljs-string">mkdir</span> <span class="hljs-string">-p</span> <span class="hljs-string">./registry/data</span>

<span class="hljs-comment"># install htpasswd</span>
<span class="hljs-string">sudo</span> <span class="hljs-string">apt</span> <span class="hljs-string">install</span> <span class="hljs-string">apache2-utils</span>

<span class="hljs-comment"># create a password file. username: busy, password: bee</span>
<span class="hljs-string">htpasswd</span> <span class="hljs-string">-Bbn</span> <span class="hljs-string">busy</span> <span class="hljs-string">bee</span> <span class="hljs-string">&gt;</span> <span class="hljs-string">./registry/registry.password</span>
</code></pre>
<p>Now we can start the registry container. If you see this message, than everything is working as it should:</p>
<pre><code class="lang-yaml"><span class="hljs-string">docker-compose</span> <span class="hljs-string">up</span>

<span class="hljs-comment"># successfull run should output something like this:</span>
<span class="hljs-comment"># registry | level=info msg="listening on [::]:5000"</span>
</code></pre>
<h3 id="heading-step-3-run-nginx-for-handling-tls">Step 3: Run NGINX for handling TLS</h3>
<p>As mentioned earlier, we can use NGINX to handle TLS and forward requests to the registry container.</p>
<p>The Docker Registry requires a valid trusted SSL certificate to work. You can use something like <a target="_blank" href="https://letsencrypt.org/">Let's Encrypt</a> or obtain it manually. Make sure you have a domain name pointing to your server (<strong>registry.pliutau.com</strong> in my case). For this demo I already obtained the certificates using <a target="_blank" href="https://certbot.eff.org/">certbot</a> and put it in the <strong>./nginx/certs</strong> directory.</p>
<p>Since we're running our Docker Registry in a container, we can run NGINX in a container as well by adding the following service to the <strong>compose.yaml</strong> file:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">services:</span>
  <span class="hljs-attr">registry:</span>
    <span class="hljs-comment"># ...</span>
  <span class="hljs-attr">nginx:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">nginx:latest</span>
    <span class="hljs-attr">depends_on:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">registry</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-comment"># mount the nginx configuration</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./nginx/nginx.conf:/etc/nginx/nginx.conf</span>
      <span class="hljs-comment"># mount the certificates obtained from Let's Encrypt</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./nginx/certs:/etc/nginx/certs</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"443:443"</span>
</code></pre>
<p>Our <strong>nginx.conf</strong> file could look like this:</p>
<pre><code class="lang-yaml"><span class="hljs-string">worker_processes</span> <span class="hljs-string">auto;</span>

<span class="hljs-string">events</span> {
    <span class="hljs-string">worker_connections</span> <span class="hljs-number">1024</span><span class="hljs-string">;</span>
}

<span class="hljs-string">http</span> {
    <span class="hljs-string">upstream</span> <span class="hljs-string">registry</span> {
        <span class="hljs-string">server</span> <span class="hljs-string">registry:5000;</span>
    }

    <span class="hljs-string">server</span> {
        <span class="hljs-string">server_name</span> <span class="hljs-string">registry.pliutau.com;</span>
        <span class="hljs-string">listen</span> <span class="hljs-number">443</span> <span class="hljs-string">ssl;</span>

        <span class="hljs-string">ssl_certificate</span> <span class="hljs-string">/etc/nginx/certs/fullchain.pem;</span>
        <span class="hljs-string">ssl_certificate_key</span> <span class="hljs-string">/etc/nginx/certs/privkey.pem;</span>

        <span class="hljs-string">location</span> <span class="hljs-string">/</span> {
            <span class="hljs-comment"># important setting for large images</span>
            <span class="hljs-string">client_max_body_size</span>                <span class="hljs-string">1000m;</span>

            <span class="hljs-string">proxy_pass</span>                          <span class="hljs-string">http://registry;</span>
            <span class="hljs-string">proxy_set_header</span>  <span class="hljs-string">Host</span>              <span class="hljs-string">$http_host;</span>
            <span class="hljs-string">proxy_set_header</span>  <span class="hljs-string">X-Real-IP</span>         <span class="hljs-string">$remote_addr;</span>
            <span class="hljs-string">proxy_set_header</span>  <span class="hljs-string">X-Forwarded-For</span>   <span class="hljs-string">$proxy_add_x_forwarded_for;</span>
            <span class="hljs-string">proxy_set_header</span>  <span class="hljs-string">X-Forwarded-Proto</span> <span class="hljs-string">$scheme;</span>
            <span class="hljs-string">proxy_read_timeout</span>                  <span class="hljs-number">900</span><span class="hljs-string">;</span>
        }
    }
}
</code></pre>
<h3 id="heading-ready-to-go">Ready to go!</h3>
<p>After these steps we can run our registry and Nginx containers.</p>
<pre><code class="lang-bash">docker-compose up
</code></pre>
<p>Now, on the client side, you can push and pull the images from your registry. But first we need to login to the registry.</p>
<pre><code class="lang-bash">docker login registry.pliutau.com

<span class="hljs-comment"># Username: busy</span>
<span class="hljs-comment"># Password: bee</span>
<span class="hljs-comment"># Login Succeeded</span>
</code></pre>
<p>Time to build and push our image to our self-hosted registry:</p>
<pre><code class="lang-bash">docker build -t registry.pliutau.com/pliutau/hello-world:v0 .

docker push registry.pliutau.com/pliutau/hello-world:v0
<span class="hljs-comment"># v0: digest: sha256:a56ea4... size: 738</span>
</code></pre>
<p>On your server you can check the uploaded images in the data folder:</p>
<pre><code class="lang-bash">ls -la ./registry/data/docker/registry/v2/repositories/
</code></pre>
<h3 id="heading-other-options">Other options</h3>
<p>Following the example above, you can also run the registry on Kubernetes. Or you could use a managed registry service like <a target="_blank" href="https://goharbor.io/">Harbor</a>, which is an open-source registry that provides advanced security features and is compatible with Docker and Kubernetes.</p>
<p>Also, if you want to have a UI for your self-hosted registry, you could use a project like <a target="_blank" href="https://github.com/Joxit/docker-registry-ui">joxit/docker-registry-ui</a> and run it in a separate container.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Self-hosted Container Registries allow you to have complete control over your registry and the way it's deployed. At the same time it comes with a cost of maintaining and securing the registry.</p>
<p>Whatever your reasons for running a self-hosted registry, you now know how it's done. From here you can compare the different options and choose the one that best fits your needs.</p>
<p>You can find the full source code for this demo on <a target="_blank" href="https://github.com/plutov/packagemain/tree/master/26-self-hosted-container-registry">GitHub</a>. Also, you can watch it as a video on <a target="_blank" href="https://www.youtube.com/watch?v=TGLfQZ9qRaI">our YouTube channel</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Secure Your Web Server with Continuous Integration Using NGINX and CircleCI ]]>
                </title>
                <description>
                    <![CDATA[ Web servers are responsible for delivering web pages and various resources to clients through the internet. They can exist either as software or hardware components. But unfortunately, they often become targets for hackers and malicious individuals s... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/secure-web-server-with-continuous-integration-using-nginx-and-circleci/</link>
                <guid isPermaLink="false">66d45d5e230dff016690579b</guid>
                
                    <category>
                        <![CDATA[ Continuous Integration ]]>
                    </category>
                
                    <category>
                        <![CDATA[ nginx ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Security ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Abraham Dahunsi ]]>
                </dc:creator>
                <pubDate>Fri, 19 Jan 2024 16:46:59 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/01/feature-image.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Web servers are responsible for delivering web pages and various resources to clients through the internet. They can exist either as software or hardware components.</p>
<p>But unfortunately, they often become targets for hackers and malicious individuals seeking to exploit any vulnerabilities to compromise data and disrupt functionality. As a result, you'll need to prioritize the security of your web server by updating it and implementing safeguards against threats.</p>
<p>To enhance the security of your web server, one effective approach is to use <a target="_blank" href="https://www.freecodecamp.org/news/what-is-ci-cd/">Continous Integration</a> (CI). CI is a DevOps technique that allows the automated merging of code modifications from software engineers into a single repository. This practice enhances code quality, minimizes bugs and speeds up code delivery.</p>
<p>By using CI, you can automate the testing, building, and deployment processes for your web servers’ code and configuration. You can also ensure that your web server consistently maintains a stable state.</p>
<p>In this tutorial, I'll guide you through the process of strengthening the security of your web server by using two popular and powerful tools: <a target="_blank" href="https://www.freecodecamp.org/news/nginx/">NGINX</a> and CircleCI.</p>
<p>NGINX, which is an open source web server, provides a range of features and modules that can greatly enhance the security of your web server. These include SSL/TLS encryption, security headers, and support for HTTP/2.</p>
<p>On the other hand, CircleCI offers both cloud-based and self-hosted options for Continous Integration (CI) and Continuous Delivery (CD), enabling seamless deployment processes.</p>
<p>By following this guide, you will learn how to:</p>
<ul>
<li><p>Configure NGINX to use SSL/TLS encryption and security headers</p>
</li>
<li><p>Create a GitHub repository and push your NGINX configuration files to it</p>
</li>
<li><p>Create a CircleCI project and link it to your GitHub repository</p>
</li>
<li><p>Create a CircleCI configuration file and define your CI pipeline</p>
</li>
<li><p>Test and deploy your web server with CircleCI</p>
</li>
</ul>
<h3 id="heading-here-is-what-well-cover">Here is what we'll cover:</h3>
<ul>
<li><a class="post-section-overview" href="#">Prerequisites</a></li>
</ul>
<ul>
<li><a class="post-section-overview" href="#Configure-NGINX-to-Use-SSL/TLS-Encryption">Step 1: Configure NGINX to Use SSL/TLS Encryption</a></li>
</ul>
<ul>
<li><a class="post-section-overview" href="#">Step 2: Configure NGINX to Include Security Headers</a></li>
</ul>
<ul>
<li><a class="post-section-overview" href="#">Step 3: Create a GitHub Repository and Push Your NGINX Configuration</a></li>
</ul>
<ul>
<li><a class="post-section-overview" href="#">Step 4: Create a CircleCI Project and Link it to Your GitHub Repository</a></li>
</ul>
<ul>
<li><a class="post-section-overview" href="#">Step 5: Create a CircleCI Configuration File and Define Your CI Pipeline</a></li>
</ul>
<ul>
<li><a class="post-section-overview" href="#">Step 6: Test and Deploy Your Web Server with CircleCI</a></li>
</ul>
<p>Let's get started!</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before you start this guide, you need to ensure you have the following:</p>
<ul>
<li><p>A web server running NGINX. If you don't have one, you can follow this <a target="_blank" href="https://www.digitalocean.com/community/tutorials/how-to-install-nginx-on-ubuntu-20-04">guide</a> to install NGINX on Ubuntu 20.04. You can also use any other operating system or cloud provider that supports NGINX.</p>
</li>
<li><p>A GitHub account. If you don't have one, you can sign up for free <a target="_blank" href="https://github.com/join">here</a>.</p>
</li>
<li><p>A CircleCI account. If you don't have one, you can sign up for free <a target="_blank" href="https://circleci.com/signup/">here</a>. You will also need to link your GitHub account to your CircleCI account.</p>
</li>
<li><p>Some basic knowledge of <a target="_blank" href="https://www.freecodecamp.org/news/learn-web-development-with-this-free-20-hour-course/">web development</a> and <a target="_blank" href="https://www.freecodecamp.org/news/helpful-linux-commands-you-should-know/">Linux commands</a>. You should be familiar with the concepts of web servers, SSL/TLS encryption, security headers, and CI. You should also be comfortable with using the command line and editing configuration files.</p>
</li>
</ul>
<p>Once you have these, you are ready to proceed with the next steps.</p>
<h2 id="heading-step-1-configure-nginx-to-use-ssltls-encryption">Step 1: Configure NGINX to Use SSL/TLS Encryption</h2>
<p>SSL/TLS encryption ensures the transmission of data between your web server and clients. It safeguards against interception or manipulation of information. It also plays a role in verifying the identity and reliability of your web server.</p>
<p>You need an SSL/TLS certificate to use SSL/TLS for your web server. An SSL/TLS certificate contains information about your web server, such as its domain name, owner, and public key. The validity of the certificate is verified through the unique digital signature from a Certificate Authority (CA).</p>
<p>You can either purchase an SSL/TLS certificate from a commercial CA, such as DigiCert, Symantec, or GlobalSign, or you can just get one for free from a non-profit CA, such as Let's Encrypt. You can also create your own self-signed certificate, but this is not recommended for production use, as it will not be trusted by most browsers and clients.</p>
<p>In this guide, you will use Let's Encrypt to get a free SSL/TLS certificate for your web server. To use Let's Encrypt, you need to install a software client on your web server that can communicate with the CA and perform the necessary tasks.</p>
<p>One of the most common and recommended clients for Let's Encrypt is Certbot. Certbot is a command-line tool that can automatically request, install, and renew certificates for your web server. It can also configure your web server to use the certificates and enable HTTPS.</p>
<p>To install Certbot on your web server, run the following commands:</p>
<pre><code class="lang-bash">sudo apt update
sudo apt install certbot python3-certbot-nginx
</code></pre>
<p>After installing Certbot, use it to request and install a certificate for your web server. You need to provide your domain name and your email address for the certificate.</p>
<p>To request and install a certificate for your web server, run the following command:</p>
<pre><code class="lang-bash">sudo certbot --nginx -d yourdomain.com
</code></pre>
<p>Replace yourdomain.com with your actual domain name.</p>
<p>Follow the prompts and answer the questions. Certbot will automatically verify your domain ownership, obtain a certificate, and install it on your web server. It will also ask you whether you want to redirect all HTTP traffic to HTTPS. Choose option 2 to enable the redirection.</p>
<p>After the process is completed, you will see a message like this:</p>
<p><img src="https://i.ibb.co/wBVfh1R/carbon-1.png" alt="certbot-success message" width="1623" height="628" loading="lazy"></p>
<p>You have now successfully configured NGINX to use SSL/TLS encryption with a certificate from Let's Encrypt. You can now access your web server using HTTPS and see the lock icon in your browser.</p>
<p><img src="https://i.ibb.co/b3pqJBB/secureverification2.png" alt="Lock Icon" width="755" height="387" loading="lazy"></p>
<p>You can also test your web server security using online tools, such as SSL Labs. You should see a grade of A or higher.</p>
<p>Note: Let's Encrypt certificates are valid for 90 days. Certbot can automatically renew them for you before they expire.</p>
<p>To enable the automatic renewal, you need to create a CRON job or a systemd timer that runs the following command at least once per day:</p>
<pre><code class="lang-bash">sudo certbot renew
</code></pre>
<p>You can also test the renewal process manually by running the following command:</p>
<pre><code class="lang-bash">sudo certbot renew --dry-run
</code></pre>
<p>This will perform a trial run without making any changes.</p>
<p>If you encounter any errors or issues, you can check the <a target="_blank" href="https://eff-certbot.readthedocs.io/en/latest/">Certbot documentation</a> or the <a target="_blank" href="https://community.letsencrypt.org/">Let's Encrypt community forum</a> for help.</p>
<h2 id="heading-step-2-configure-nginx-to-include-security-headers">Step 2: Configure NGINX to Include Security Headers</h2>
<p>Security headers help instruct the browser to apply certain security policies or restrictions when handling your web content. They can prevent or mitigate common web attacks, such as cross-site scripting (XSS), clickjacking, content injection, and more.</p>
<p>In this step, you will be adding security headers to your NGINX configuration. These headers include X Frame Options, X Content Type Options, X XSS Protection, and Content Security Policy.</p>
<h3 id="heading-x-frame-options">X-Frame-Options</h3>
<p>The X-Frame-Options header tells the browser whether or not to allow your web page to be displayed in a frame, iframe, embed, or object element. This can help you prevent clickjacking attacks, where an attacker overlays a hidden frame on top of your web page and tricks the user into clicking on it.</p>
<p>There are three possible values for this header:</p>
<ul>
<li><p>DENY: This value prevents your web page from being displayed in any frame.</p>
</li>
<li><p>SAMEORIGIN: This value allows your web page to be displayed in a frame only if the frame is from the same origin as your web page.</p>
</li>
<li><p>ALLOW-FROM URI: This value allows your web page to be displayed in a frame only if the frame is from the specified URI.</p>
</li>
</ul>
<p>To enable the X-Frame-Options header in NGINX, add the following line to your server block in your NGINX configuration file (/etc/nginx/sites-enabled/example.conf):</p>
<pre><code class="lang-nginx"><span class="hljs-attribute">add_header</span> X-Frame-Options <span class="hljs-string">"SAMEORIGIN"</span>;
</code></pre>
<p>This will allow your web page to be displayed in a frame only if the frame is from the same origin as your web page. You can change the value to DENY or ALLOW-FROM uri according to your needs.</p>
<p>Save the file and restart NGINX to apply the changes.</p>
<h3 id="heading-x-content-type-options">X-Content-Type-Options</h3>
<p>The X Content Type Options header instructs the browser to perform MIME-type sniffing. This feature attempts to determine the content type of a resource by analyzing its content or file extension.</p>
<p>By using this header you can safeguard against content injection attacks. These attacks involve uploading a file with a content type to exploit the browser’s interpretation of it.</p>
<p>There is only one possible value for this header:</p>
<ul>
<li>nosniff: This value prevents the browser from performing MIME type sniffing.</li>
</ul>
<p>To enable the X-Content-Type-Options header in NGINX, add the following line to your server block in your NGINX configuration file (/etc/nginx/sites-enabled/example.conf):</p>
<pre><code class="lang-nginx"><span class="hljs-attribute">add_header</span> X-Content-Type-Options <span class="hljs-string">"nosniff"</span>;
</code></pre>
<p>This will prevent the browser from performing MIME type sniffing on your web resources.</p>
<p>Save the file and restart NGINX to apply the changes.</p>
<h3 id="heading-x-xss-protection">X-XSS-Protection</h3>
<p>The X-XSS-Protection header tells the browser to enable or disable its built-in XSS filter, which can detect and block some types of XSS attacks. This can help you prevent XSS attacks, where an attacker injects malicious code into your web page that executes in the browser.</p>
<p>There are three possible values for this header:</p>
<ul>
<li><p>0: This value disables the XSS filter.</p>
</li>
<li><p>1: This value enables the XSS filter and sanitizes the page if an XSS attack is detected.</p>
</li>
<li><p>1; mode=block: This value enables the XSS filter and blocks the page if an XSS attack is detected.</p>
</li>
</ul>
<p>To enable the X-XSS-Protection header in NGINX, add the following line to your server block in your NGINX configuration file (/etc/nginx/sites-enabled/example.conf):</p>
<pre><code class="lang-nginx"><span class="hljs-attribute">add_header</span> X-XSS-Protection <span class="hljs-string">"1; mode=block"</span>;
</code></pre>
<p>This will enable the XSS filter and block the page if an XSS attack is detected. You can change the value to 0 or 1 according to your needs.</p>
<p>Save the file and restart NGINX to apply the changes.</p>
<h3 id="heading-content-security-policy">Content-Security-Policy</h3>
<p>The Content-Security-Policy header tells the browser to enforce a set of rules that restrict what sources and types of content can be loaded and executed on your web page. This can help you prevent XSS, content injection, and other types of attacks that rely on loading malicious or untrusted content.</p>
<p>The value of this header is a complex policy that consists of multiple directives and values. Each directive specifies a type of content and a list of sources that are allowed or disallowed for that content.</p>
<p>For example, the following policy allows only scripts styles from the same origin, and images from the same origin or yourdomain.com:</p>
<pre><code class="lang-nginx">Content-Security-Policy: default-<span class="hljs-attribute">src</span> <span class="hljs-string">'none'</span>; script-<span class="hljs-attribute">src</span> <span class="hljs-string">'self'</span>; style-<span class="hljs-attribute">src</span> <span class="hljs-string">'self'</span>; img-<span class="hljs-attribute">src</span> <span class="hljs-string">'self'</span> yourdomain.com;
</code></pre>
<p>The Content-Security-Policy header is very powerful and flexible, but also very complicated and error-prone. You need to carefully design and test your policy to ensure that it does not break your web functionality or introduce new vulnerabilities. You can use tools like CSP Evaluator or CSP Scanner to check and improve your policy.</p>
<p>To enable the Content-Security-Policy header in NGINX, add the following line to your server block in your NGINX configuration file (/etc/nginx/sites-enabled/example.conf):</p>
<pre><code class="lang-nginx"><span class="hljs-attribute">add_header</span> Content-Security-Policy <span class="hljs-string">"default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self' yourdomain.com;"</span>;
</code></pre>
<p>This will enforce the policy that you described above. You can change the policy according to your needs.</p>
<p>Save the file and restart NGINX to apply the changes.</p>
<h2 id="heading-step-3-create-a-github-repository-and-push-your-nginx-configuration">Step 3: Create a GitHub Repository and Push Your NGINX Configuration</h2>
<p>To create a GitHub repository and push your NGINX configuration files to it, follow these steps:</p>
<h3 id="heading-create-a-github-repository">Create a GitHub repository</h3>
<p>First, log in to your GitHub account and go to the GitHub homepage.</p>
<p>Click on the plus icon in the top right corner of the page, and select "New repository" from the dropdown menu.</p>
<p><img src="https://i.ibb.co/vkWX0BJ/dropdownmenu-edited.png" alt="Dropdown Menu" width="1486" height="383" loading="lazy"></p>
<p>On the next page, enter a name for your repository in the "Repository name" field. This should be a short and descriptive name that accurately reflects the contents of the repository. For example, you can name it "nginx-config".</p>
<p>In the "Description" field, you can enter a longer description of the repository if you want. This is optional, but it can be helpful to provide more information about the purpose of the repository.</p>
<p>For example, you can write "A repository for storing and managing my NGINX configuration files".</p>
<p>You can set the visibility to whatever you prefer. If you want others to be able to see your work, set it to "Public". Otherwise, set it to "Private".</p>
<p>Leave the "Initialize this repository with a README" option unchecked, as you want to create an empty repository.</p>
<p><img src="https://i.ibb.co/SQVJbqh/settingupnewrepo.png" alt="Settting up New Repository" width="1882" height="786" loading="lazy"></p>
<p>Click on the "Create repository" button to create the repository.</p>
<p>Your new empty repository will be created and you will be taken to the repository page.</p>
<h3 id="heading-push-your-nginx-configuration-files-to-the-github-repository">Push your NGINX configuration files to the GitHub repository</h3>
<p>On your web server, navigate to the directory where your NGINX configuration files are located. By default, this is /etc/nginx on most Linux distributions.</p>
<p>Initialize a new Git repository in this directory by running the following command:</p>
<pre><code class="lang-bash">git init
</code></pre>
<p>This will create a new .git directory in the current directory, which will be used to store all the version control information for your project.</p>
<p>Add all the configuration files that you want to include in the repository by running the following command:</p>
<pre><code class="lang-bash">git add .
</code></pre>
<p>This will add all the files in the current directory and its subdirectories to the repository. You can also specify individual files to be added by replacing the (.) with the file names, separated by spaces.</p>
<p>Then commit the files to the repository by running the following command:</p>
<pre><code class="lang-bash">git commit -m <span class="hljs-string">"Initial commit"</span>
</code></pre>
<p>This will create the first commit in the repository, which will include all the files that were added in the previous step. The -m flag is used to specify a commit message, which should briefly describe the changes that were made in this commit.</p>
<p>Go back to your GitHub repository page and copy the URL of your repository. You can find it under the "Code" section. It should look something like this:</p>
<p><img src="https://i.ibb.co/GThsRSV/code-Button.png" alt="github-url" width="675" height="532" loading="lazy"></p>
<p>On your web server, add the URL of your GitHub repository as a remote for your Git repository by running the following command:</p>
<pre><code class="lang-bash">git remote add origin https://github.com/username/nginx-config.git
</code></pre>
<p>Replace username with your GitHub username and nginx-config with your repository name. The origin is the name of the remote, which you can change to anything you want.</p>
<p>Push your local Git repository to the GitHub repository by running the following command:</p>
<pre><code class="lang-bash">git push -u origin master
</code></pre>
<p>This will push your master branch, which is the default branch in Git, to the origin remote, which is the GitHub repository that you created. The -u flag is used to set the upstream for your branch, which means that you can use git push or git pull without specifying the remote or the branch in the future.</p>
<p>Enter your GitHub username and password when prompted. If you have enabled two-factor authentication, you will need to use a personal access token instead of your password. You can generate one from your GitHub settings page.</p>
<p>You have successfully created a GitHub repository and pushed your NGINX configuration files to it. You can now view and manage your configuration files on GitHub.</p>
<h2 id="heading-step-4-create-a-circleci-project-and-link-it-to-your-github-repository">Step 4: Create a CircleCI Project and Link it to Your GitHub Repository</h2>
<p>CircleCI is a platform that offers cloud-based and self-hosted options for continuous integration and delivery. It allows you to create and run pipelines that automate and streamline your web server deployment and update process.</p>
<p>To use CircleCI, you need to create a CircleCI project and link it to your GitHub repository. This will enable CircleCI to access your code and configuration files, and trigger builds whenever you push to GitHub.</p>
<p>To create a CircleCI project and link it to your GitHub repository, follow these steps:</p>
<h3 id="heading-sign-up-for-circleci-and-connect-your-github-account">Sign up for CircleCI and connect your GitHub account</h3>
<p>Start by logging in to your CircleCI account or sign up for free <a target="_blank" href="https://circleci.com/login/">here</a>.</p>
<p>On the CircleCI dashboard, click on the "Create Project" button in the top right corner of the page.</p>
<p><img src="https://i.ibb.co/wyPsNjk/project-button.png" alt="Create project Button" width="1865" height="648" loading="lazy"></p>
<p>On the next page, select "GitHub" as your version control provider and click on the "Connect with GitHub" button.</p>
<p><img src="https://i.ibb.co/MhywfHF/choosing-github.png" alt="Choosing Github" width="1907" height="786" loading="lazy"></p>
<p>On the next page, authorize CircleCI to access your GitHub account by clicking on the "Authorize circleci" button.</p>
<p>On the next page, Enter a “Project Name” and follow the remaining instructions to successfully create a CircleCI project.</p>
<p><img src="https://i.ibb.co/MkYZBTF/Project-Name.png" alt="Enter Project Name" width="1432" height="290" loading="lazy"></p>
<h3 id="heading-create-a-circleci-project-and-link-it-to-your-github-repository">Create a CircleCI project and link it to your GitHub repository</h3>
<p>Next, create a new SSH key pair in your terminal:</p>
<pre><code class="lang-bash">ssh-keygen -t ed25519 -f ~/.ssh/project_key -C email@example.com
</code></pre>
<p>Then copy the private key generated:</p>
<pre><code class="lang-bash">pbcopy &lt; ~/.ssh/project_key
</code></pre>
<p>Next, enter it in the private key field:</p>
<p><img src="https://i.ibb.co/0Qs2TNJ/Private-SSH-Key.png" alt="Enter private SSH Key" width="1043" height="161" loading="lazy"></p>
<p>You will see a list of your GitHub repositories. Find the repository that you created in the previous step and click on the "Create Project" button next to it.</p>
<p>Now you will see a list of templates for different languages and frameworks. Since you are using Python and Flask, select the "Python" template and click on the "Use this config" button.</p>
<p>On the next page, you will see the generated CircleCI configuration file (config.yml) that defines your pipeline. You can review and edit the file if you want, or leave it as it is for now. Click on the "Start building" button to create the project and link it to your GitHub repository.</p>
<p>Your new CircleCI project will be created and linked to your GitHub repository.</p>
<p>You have now successfully created a CircleCI project and linked it to your GitHub repository. You can now configure and run your pipeline on CircleCI.</p>
<h2 id="heading-step-5-create-a-circleci-configuration-file-and-define-your-ci-pipeline">Step 5: Create a CircleCI Configuration File and Define Your CI Pipeline</h2>
<p>A CircleCI configuration file is a YAML file that defines your CI pipeline. A CI pipeline is a sequence of jobs that run whenever you push changes to your GitHub repository. Each job consists of steps that perform specific tasks, such as running commands, installing dependencies, or deploying your web server.</p>
<p>In this step, you will create a CircleCI configuration file and define your CI pipeline. You will use the Python template that you selected in the previous step as a starting point, and modify it to suit our needs. You will also explain what each step in the pipeline does and how it helps to automate and secure your web server deployment.</p>
<h3 id="heading-create-a-circleci-configuration-file">Create a CircleCI configuration file</h3>
<p>On your web server, navigate to the directory where your NGINX configuration files are located. By default, this is /etc/nginx on most Linux distributions.</p>
<p>Create a new directory called .circleci in this directory by running the following command:</p>
<pre><code class="lang-bash">mkdir .circleci
</code></pre>
<p>This is where you will store our CircleCI configuration file.</p>
<p>Then create a new file called config.yml in the .circleci directory by running the following command:</p>
<pre><code class="lang-bash">touch .circleci/config.yml
</code></pre>
<p>This is your CircleCI configuration file.</p>
<p>Open the config.yml file with your preferred text editor and paste the following code:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">version:</span> <span class="hljs-number">2.1</span>

<span class="hljs-attr">jobs:</span>
  <span class="hljs-attr">build:</span>
    <span class="hljs-attr">docker:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">image:</span> <span class="hljs-string">cimg/python:3.9</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">checkout</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">run:</span>
          <span class="hljs-attr">name:</span> <span class="hljs-string">Install</span> <span class="hljs-string">dependencies</span>
          <span class="hljs-attr">command:</span> <span class="hljs-string">|
            pip install -r requirements.txt
</span>      <span class="hljs-bullet">-</span> <span class="hljs-attr">run:</span>
          <span class="hljs-attr">name:</span> <span class="hljs-string">Run</span> <span class="hljs-string">tests</span>
          <span class="hljs-attr">command:</span> <span class="hljs-string">|
            pytest
</span>  <span class="hljs-attr">deploy:</span>
    <span class="hljs-attr">machine:</span>
      <span class="hljs-attr">image:</span> <span class="hljs-string">ubuntu-2004:202101-01</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">checkout</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">add_ssh_keys:</span>
          <span class="hljs-attr">fingerprints:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-string">"YOUR_FINGERPRINT"</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">run:</span>
          <span class="hljs-attr">name:</span> <span class="hljs-string">Deploy</span> <span class="hljs-string">Nginx</span> <span class="hljs-string">configuration</span>
          <span class="hljs-attr">command:</span> <span class="hljs-string">|
            scp -r nginx root@YOUR_IP:/etc
            ssh root@YOUR_IP "systemctl restart nginx"
</span>
<span class="hljs-attr">workflows:</span>
  <span class="hljs-attr">version:</span> <span class="hljs-number">2</span>
  <span class="hljs-attr">build-and-deploy:</span>
    <span class="hljs-attr">jobs:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">build</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">deploy:</span>
          <span class="hljs-attr">requires:</span>
            <span class="hljs-bullet">-</span> <span class="hljs-string">build</span>
          <span class="hljs-attr">filters:</span>
            <span class="hljs-attr">branches:</span>
              <span class="hljs-attr">only:</span> <span class="hljs-string">main</span>
</code></pre>
<p>This is your CircleCI configuration file that defines your CI pipeline. You will explain each part of the file in the next section.</p>
<p>Finally, fave and close the file.</p>
<h3 id="heading-define-your-ci-pipeline">Define your CI pipeline</h3>
<p>Let's go through each part of the config.yml file and see what it does.</p>
<ul>
<li><p>Line 1: This indicates the version of the CircleCI platform you are using. 2.1 is the most recent version.</p>
</li>
<li><p>Line 3: The jobs level contains a collection of children, representing your jobs. You specify the names for these jobs, for example, build, test, deploy.</p>
</li>
<li><p>Line 6: This is the Docker image. The example shows cimg/python:3.9, which is a CircleCI-provided image that contains Python 3.9 and other common tools.</p>
</li>
<li><p>Line 9: The run directive executes a shell command or script. You can specify a name and a command for each run directive.</p>
</li>
<li><p>Line 11: The command attribute is a list of shell commands that you want to execute. In this case, you are installing the dependencies for your web application using pip.</p>
</li>
<li><p>Line 12: This is another run directive that runs the tests for your web application using pytest.</p>
</li>
<li><p>Line 13: deploy is the second child in the jobs collection. This job is responsible for deploying your NGINX configuration to your web server.</p>
</li>
<li><p>Line 14: This specifies that you are using a machine executor for this job. A machine executor provides a full virtual machine with root access and various tools installed.</p>
</li>
<li><p>Line 15: This is the machine image. The example shows ubuntu-2004:202101-01, which is a CircleCI-provided image that contains Ubuntu 20.04 and other common tools.</p>
</li>
<li><p>Line 16: The steps collection is a list of run directives and other commands that you want to execute in this job.</p>
</li>
<li><p>Line 18: The add_ssh_keys command adds your SSH keys to the machine. You need to provide the fingerprints of the keys that you want to use. You can generate and add SSH keys from your CircleCI settings page.</p>
</li>
<li><p>Line 21: The command attribute is a list of shell commands that you want to execute. In this case, you are using SCP to copy your NGINX configuration files from the machine to your web server, and SSH to restart the NGINX service on your web server. You need to replace YOUR_FINGERPRINT with the fingerprint of your SSH key, and YOUR_IP with the IP address of your web server.</p>
</li>
<li><p>Line 24: This indicates the version of the workflow syntax you are using. 2 is the most recent version.</p>
</li>
<li><p>Line 29: The required attribute specifies the dependencies of this job. In this case, you are saying that the deploy job requires the build job to finish successfully before running.</p>
</li>
<li><p>Line 30: The filters attribute specifies the conditions for running this job. In this case, you are saying that the deploy job should only run on the main branch of our GitHub repository.</p>
</li>
</ul>
<h3 id="heading-push-your-circleci-configuration-file-to-your-github-repository">Push your CircleCI configuration file to your GitHub repository</h3>
<p>On your web server, add, commit, and push your CircleCI configuration file to your GitHub repository by running the following commands:</p>
<pre><code class="lang-bash">git add .circleci/config.yml
git commit -m <span class="hljs-string">"Add CircleCI config file"</span>
git push origin main
</code></pre>
<p>This will trigger a new build on CircleCI and run your CI pipeline.</p>
<p>Go to your CircleCI dashboard and click on the build-and-deploy workflow.</p>
<p>You can click on each job to see the details and logs of the steps.</p>
<p>Wait for the workflow to finish.</p>
<p>You have successfully created a CircleCI configuration file and defined your CI pipeline. You can now automate and secure your web server deployment with CircleCI. You can also modify and improve your configuration file according to your needs.</p>
<h2 id="heading-step-6-test-and-deploy-your-web-server-with-circleci">Step 6: Test and Deploy Your Web Server with CircleCI</h2>
<p>Now that you have created a CircleCI project and a configuration file for your CI pipeline, you can test and deploy your web server with CircleCI. You can trigger and monitor your CI pipeline from the CircleCI web app or the command line. You can also verify that your web server is deployed and secured correctly by using online tools or by accessing your web server using HTTPS.</p>
<h3 id="heading-trigger-and-monitor-your-ci-pipeline-from-the-circleci-web-app">Trigger and monitor your CI pipeline from the CircleCI web app</h3>
<p>To trigger and monitor your CI pipeline from the CircleCI web app, follow these steps:</p>
<ul>
<li><p>Go to the CircleCI dashboard.</p>
</li>
<li><p>On the dashboard, you will see a list of your projects and pipelines. Find the project that you created in the previous step and click on it.</p>
</li>
<li><p>On the project page, you will see a list of your branches and workflows. Find the branch that you pushed your CircleCI configuration file to in the previous step and click on the build-and-deploy workflow.</p>
</li>
<li><p>On the workflow page, you will see a graphical representation of your pipeline, showing the status and duration of each job and step. You can click on each job or step to see the details and logs of the commands that were executed.</p>
</li>
<li><p>Wait for the workflow to finish. If everything goes well, you will see a green check mark next to each job and step, indicating that they were successful.</p>
</li>
</ul>
<p>You have successfully triggered and monitored your CI pipeline from the CircleCI web app. You can also trigger and monitor your CI pipeline from the command line.</p>
<h3 id="heading-trigger-and-monitor-your-ci-pipeline-from-the-command-line">Trigger and monitor your CI pipeline from the command line</h3>
<p>To trigger and monitor your CI pipeline from the command line, follow these steps:</p>
<ul>
<li><p>On your web server, navigate to the directory where your NGINX configuration files are located. By default, this is /etc/nginx on most Linux distributions.</p>
</li>
<li><p>Make some changes to your configuration files, such as adding or removing security headers, and save them.</p>
</li>
<li><p>Add, commit, and push your changes to your GitHub repository by running the following commands:</p>
</li>
</ul>
<pre><code class="lang-bash">git add .
git commit -m <span class="hljs-string">"Update Nginx configuration"</span>
git push origin main
</code></pre>
<p>This will trigger a new build on CircleCI and run your CI pipeline.</p>
<p>To monitor your CI pipeline from the command line, you can use the CircleCI CLI, which is a tool that allows you to interact with CircleCI from your terminal. You can install the CircleCI CLI by following the instructions on the official website.</p>
<p>After installing the CircleCI CLI, you can use the <code>circleci</code> command to perform various actions, such as listing your projects, pipelines, workflows, jobs, and artifacts. You can also use the --help flag to see the available options and arguments for each command.</p>
<p>To monitor your CI pipeline from the command line, you can use the circleci pipeline command to list and describe your pipelines.</p>
<p>For example, you can run the following command to list the pipelines for your project:</p>
<pre><code class="lang-bash">circleci pipeline list --org-slug &lt;VCS&gt;/&lt;your-vcs-org-or-username&gt; --project-slug &lt;VCS&gt;/&lt;your-repo-name&gt;
</code></pre>
<p>Replace <code>&lt;VCS&gt;</code> with either gh or bb depending on your version control system. Replace <code>&lt;your-vcs-org-or-username&gt;</code> with your GitHub or Bitbucket organization or username. Replace <code>&lt;your-repo-name&gt;</code> with your repository name. You will see something like this:</p>
<p><img src="https://i.ibb.co/JKqc0cn/carbon-5.png" alt="Command Output" width="1631" height="358" loading="lazy"></p>
<p>You can use the pipeline ID or number to describe a specific pipeline and see its details, such as the status, workflows, jobs, and steps. For example, you can run the following command to describe the latest pipeline for your project:</p>
<pre><code class="lang-bash">circleci pipeline describe --org-slug &lt;VCS&gt;/&lt;your-vcs-org-or-username&gt; --project-slug &lt;VCS&gt;/&lt;your-repo-name&gt; --pipeline-number &lt;number&gt;
</code></pre>
<p>Replace <code>&lt;number&gt;</code> with the pipeline number that you want to describe.</p>
<p>Wait for the pipeline to finish. If everything goes well, you will see a success message for each job and step, indicating that they were successful.</p>
<p>You have successfully triggered and monitored your CI pipeline from the command line. You can also verify that your web server is deployed and secured correctly.</p>
<h3 id="heading-verify-that-your-web-server-is-deployed-and-secured-correctly">Verify that your web server is deployed and secured correctly</h3>
<p>To verify that your web server is deployed and secured correctly, you can use online tools or access your web server using HTTPS. Here are some examples:</p>
<p>To verify that your web server is using the latest Nginx configuration that you pushed to GitHub, you can use a tool like <a target="_blank" href="https://curl.se/">curl</a> or <a target="_blank" href="https://www.gnu.org/software/wget/">wget</a> to make a request to your web server and inspect the response headers.</p>
<p>For example, you can run the following command to see the security headers that your web server is sending:</p>
<pre><code class="lang-bash">curl -I https://www.yourdomain.com
</code></pre>
<p>Replace yourdomain.com with your actual domain name.</p>
<p>You can compare the headers with the ones that you configured in your NGINX configuration file and see if they match.</p>
<p>To verify that your web server is using the SSL/TLS certificate that you installed with Certbot, you can use a tool like <a target="_blank" href="https://www.ssllabs.com/ssltest/">SSL Labs</a> or <a target="_blank" href="https://www.htbridge.com/ssl/">HTBridge</a> to scan your web server and check its SSL/TLS configuration and rating. You can check the details of your certificate, such as the issuer, validity, and chain. You can also check the grade of your SSL/TLS configuration, which should be A or higher.</p>
<p>To verify that your web server is accessible and functional using HTTPS, you can simply open your web browser and visit your web server using HTTPS. For example, you can go to https://www.yourdomain.com and see your web page.</p>
<p>You can check the lock icon in your browser, which indicates that your connection is secure. You can also click on the icon and see the details of your certificate and connection.</p>
<p><img src="https://i.ibb.co/dGwr57V/certificate-viewer.png" alt="Viewing Certificate" width="688" height="674" loading="lazy"></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this article, you have learned how to secure your web server using NGINX and CicleCI. NGINX and CircleCI, when used together, can provide a powerful solution for ensuring the continuous security of your web applications.</p>
<p>Stay ahead of the curve by integrating these technologies into your workflow, and empower your team to deliver secure and reliable web services.</p>
<p>Please don't forget to share this guide with your colleagues, friends, and online communities if you find it insightful.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ NGINX for Backend Developers ]]>
                </title>
                <description>
                    <![CDATA[ NGINX is a versatile and powerful open-source software used for web serving, reverse proxying, caching, load balancing, media streaming, and more. It's important to understand for backend developers. We just published a course on the freeCodeCamp.org... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/nginx/</link>
                <guid isPermaLink="false">66b205e2eea9870582e16cc0</guid>
                
                    <category>
                        <![CDATA[ nginx ]]>
                    </category>
                
                    <category>
                        <![CDATA[ youtube ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Beau Carnes ]]>
                </dc:creator>
                <pubDate>Fri, 12 Jan 2024 16:46:05 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/01/nginx.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>NGINX is a versatile and powerful open-source software used for web serving, reverse proxying, caching, load balancing, media streaming, and more. It's important to understand for backend developers.</p>
<p>We just published a course on the freeCodeCamp.org YouTube channel that will teach you what you need to know about NGINX. Laith Harb developed this course. He is an experienced software developer and amazing teacher.</p>
<p>This course is designed to offer a comprehensive guide to understanding and using NGINX, a high-performance web server, reverse proxy, and email (IMAP/POP3) proxy.</p>
<p>NGINX is known for its high performance, stability, rich feature set, simple configuration, and low resource consumption. It is widely used to ensure fast and reliable web and application delivery.</p>
<p>Here are some places where NGINX may be used:</p>
<ul>
<li><strong>High Traffic Websites:</strong> NGINX efficiently handles large volumes of concurrent connections, making it ideal for high-traffic websites.</li>
<li><strong>Reverse Proxy Applications:</strong> It can be used as a reverse proxy server for HTTP, HTTPS, SMTP, POP3, and IMAP protocols, as well as a load balancer and an HTTP cache.</li>
<li><strong>Microservices Architecture:</strong> NGINX works well in a microservices architecture for managing communication and load balancing.</li>
<li><strong>Streaming Media:</strong> It's also well-suited for streaming media services.</li>
</ul>
<h2 id="heading-course-sections">Course Sections</h2>
<p>The course is structured into several informative sections, each focusing on a specific aspect of NGINX:</p>
<h3 id="heading-nginx-installation">NGINX Installation</h3>
<p>This section provides a step-by-step guide on how to install NGINX on various operating systems, ensuring a smooth and hassle-free start for beginners.</p>
<h3 id="heading-nginx-terminology">NGINX Terminology</h3>
<p>Understanding the key terms and concepts in NGINX is crucial. This section covers the essential terminology to help learners grasp the fundamentals of NGINX architecture and operation.</p>
<h3 id="heading-serving-static-content">Serving Static Content</h3>
<p>Learn how NGINX can be configured to serve static content, such as HTML, CSS, and JavaScript files, which is a core function of web servers.</p>
<h3 id="heading-mime-types">Mime Types</h3>
<p>This part delves into MIME types and their significance in web development, explaining how NGINX handles different data formats.</p>
<h3 id="heading-location-context">Location Context</h3>
<p>Explore the 'location' directive in NGINX, which is used to define how to process different requests URLs, a critical aspect for website management.</p>
<h3 id="heading-rewrites-and-redirects">Rewrites and Redirects</h3>
<p>Understand how to implement URL rewrites and redirects in NGINX, a common requirement for website administration and SEO optimization.</p>
<h3 id="heading-nginx-as-a-load-balancer">NGINX as a Load Balancer</h3>
<p>One of the most powerful features of NGINX is its capability to act as a load balancer. This section covers the basics of load balancing and how to set up NGINX for distributing traffic across multiple servers.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>With clear explanations, practical examples, and a structured learning path, this course is an excellent opportunity to dive deep into the world of NGINX.</p>
<p>Watch the full course on the <a target="_blank" href="https://www.youtube.com/watch?v=9t9Mp0BGnyI">freeCodeCamp.org YouTube channel</a> (1-hour watch).</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/9t9Mp0BGnyI" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ The NGINX Handbook – Learn NGINX for Beginners ]]>
                </title>
                <description>
                    <![CDATA[ A young Russian developer named Igor Sysoev was frustrated by older web servers' inability to handle more than 10 thousand concurrent requests. This is a problem referred to as the C10k problem. As an answer to this, he started working on a new web s... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/the-nginx-handbook/</link>
                <guid isPermaLink="false">66b0ab588d675d0da5f1ab85</guid>
                
                    <category>
                        <![CDATA[ nginx ]]>
                    </category>
                
                    <category>
                        <![CDATA[ servers ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Farhan Hasin Chowdhury ]]>
                </dc:creator>
                <pubDate>Mon, 26 Apr 2021 23:56:38 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/07/NGINX-Handbook-Mockup.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>A young Russian developer named <a target="_blank" href="https://en.wikipedia.org/wiki/Igor_Sysoev">Igor Sysoev</a> was frustrated by older web servers' inability to handle more than 10 thousand concurrent requests. This is a problem referred to as the <a target="_blank" href="https://en.wikipedia.org/wiki/C10k_problem">C10k problem</a>. As an answer to this, he started working on a new web server back in 2002.</p>
<p><a target="_blank" href="https://nginx.org/">NGINX</a> was first released to the public in 2004 under the terms of the <a target="_blank" href="https://en.wikipedia.org/wiki/2-clause_BSD">2-clause BSD</a> license. According to the <a target="_blank" href="https://news.netcraft.com/archives/2021/03/29/march-2021-web-server-survey.html">March 2021 Web Server Survey</a>, NGINX holds 35.3% of the market with a total of 419.6 million sites.</p>
<p>Thanks to tools like <a target="_blank" href="https://www.digitalocean.com/community/tools/nginx">NGINXConfig</a> by <a target="_blank" href="https://digitalocean.com/">DigitalOcean</a> and an abundance of pre-written configuration files on the internet, people tend to do a lot of copy-pasting instead of trying to understand when it comes to configuring NGINX.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/177962736_1410222585999736_5618677227291897851_n.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Trust me, it's not that hard...</em></p>
<p>I'm not saying that copying code is bad, but copying code without understanding is a big "no no". </p>
<p>Also NGINX is the kind of software that should be configured exactly according to the requirements of the application to be served and available resources on the host. </p>
<p>That's why instead of copying blindly, you should understand and then fine tune what you're copying – and that's where this handbook comes in.</p>
<p>After going through the entire book, you should be able to:</p>
<ul>
<li>Understand configuration files generated by popular tools as well as those found in various documentation.</li>
<li>Configure NGINX as a web server, a reverse proxy server, and a load balancer from scratch.</li>
<li>Optimize NGINX to get maximum performance out of your server.</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<ul>
<li>Familiarity with the Linux terminal and common Unix programs such as <code>ls</code>, <code>cat</code>, <code>ps</code>, <code>grep</code>, <code>find</code>, <code>nproc</code>, <code>ulimit</code> and <code>nano</code>.</li>
<li>A computer powerful enough to run a virtual machine or a $5 virtual private server.</li>
<li>Understanding of web applications and a programming language such as JavaScript or PHP.</li>
</ul>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><a class="post-section-overview" href="#heading-introduction-to-nginx">Introduction to NGINX</a></li>
<li><a class="post-section-overview" href="#heading-how-to-install-nginx">How to Install NGINX</a><ul>
<li><a class="post-section-overview" href="#heading-how-to-provision-a-local-virtual-machine">How to Provision a Local Virtual Machine</a></li>
<li><a class="post-section-overview" href="#heading-how-to-provision-a-virtual-private-server">How to Provision a Virtual Private Server</a></li>
<li><a class="post-section-overview" href="#heading-how-to-install-nginx-on-a-provisioned-server-or-virtual-machine-2">How to Install NGINX on a Provisioned Server or Virtual Machine</a></li>
</ul>
</li>
<li><a class="post-section-overview" href="#heading-introduction-to-nginxs-configuration-files">Introduction to NGINX's Configuration Files</a></li>
<li><a class="post-section-overview" href="#heading-how-to-configure-a-basic-web-server">How to Configure a Basic Web Server</a><ul>
<li><a class="post-section-overview" href="#heading-how-to-write-your-first-configuration-file">How to Write Your First Configuration File</a></li>
<li><a class="post-section-overview" href="#heading-how-to-validate-and-reload-configuration-files">How to Validate and Reload Configuration Files</a></li>
<li><a class="post-section-overview" href="#heading-how-to-understand-directives-and-contexts-in-nginx">How to Understand Directives and Contexts in NGINX</a></li>
<li><a class="post-section-overview" href="#heading-how-to-serve-static-content-using-nginx">How to Serve Static Content Using NGINX</a></li>
<li><a class="post-section-overview" href="#heading-static-file-type-handling-in-nginx">Static File Type Handling in NGINX</a></li>
<li><a class="post-section-overview" href="#heading-how-to-include-partial-config-files">How to Include Partial Config Files</a></li>
</ul>
</li>
<li><a class="post-section-overview" href="#heading-dynamic-routing-in-nginx">Dynamic Routing in NGINX</a><ul>
<li><a class="post-section-overview" href="#heading-location-matches">Location Matches</a></li>
<li><a class="post-section-overview" href="#heading-variables-in-nginx">Variables in NGINX</a></li>
<li><a class="post-section-overview" href="#heading-redirects-and-rewrites">Redirects and Rewrites</a></li>
<li><a class="post-section-overview" href="#heading-how-to-try-for-multiple-files">How to Try for Multiple Files</a></li>
</ul>
</li>
<li><a class="post-section-overview" href="#heading-logging-in-nginx">Logging in NGINX</a></li>
<li><a class="post-section-overview" href="#heading-how-to-use-nginx-as-a-reverse-proxy">How to Use NGINX as a Reverse Proxy</a><ul>
<li><a class="post-section-overview" href="#heading-nodejs-with-nginx">Node.js With NGINX</a></li>
<li><a class="post-section-overview" href="#heading-php-with-nginx">PHP With NGINX</a></li>
</ul>
</li>
<li><a class="post-section-overview" href="#heading-how-to-use-nginx-as-a-load-balancer">How to Use NGINX as a Load Balancer</a></li>
<li><a class="post-section-overview" href="#heading-how-to-optimize-nginx-for-maximum-performance">How To Optimize NGINX for Maximum Performance</a><ul>
<li><a class="post-section-overview" href="#heading-how-to-configure-worker-processes-and-worker-connections">How to Configure Worker Processes and Worker Connections</a></li>
<li><a class="post-section-overview" href="#heading-how-to-cache-static-content">How to Cache Static Content</a></li>
<li><a class="post-section-overview" href="#heading-how-to-compress-responses">How to Compress Responses</a></li>
</ul>
</li>
<li><a class="post-section-overview" href="#heading-how-to-understand-the-main-configuration-file-1">How to Understand the Main Configuration File</a></li>
<li><a class="post-section-overview" href="#heading-how-to-configure-ssl-and-http2">How To Configure SSL and HTTP/2</a><ul>
<li><a class="post-section-overview" href="#heading-how-to-configure-ssl">How To Configure SSL</a></li>
<li><a class="post-section-overview" href="#heading-how-to-enable-http2">How to Enable HTTP/2</a></li>
<li><a class="post-section-overview" href="#heading-how-to-enable-server-push">How to Enable Server Push</a></li>
</ul>
</li>
<li><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></li>
</ul>
<h2 id="heading-project-code">Project Code</h2>
<p>You can find the code for the example projects in the following repository:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/fhsinchy/nginx-handbook-projects">https://github.com/fhsinchy/nginx-handbook-projects</a></div>
<h2 id="heading-introduction-to-nginx">Introduction to NGINX</h2>
<p><a target="_blank" href="https://nginx.org/">NGINX</a> is a high performance web server developed to facilitate the increasing needs of the modern web. It focuses on high performance, high concurrency, and low resource usage. Although it's mostly known as a web server, NGINX at its core is a <a target="_blank" href="https://en.wikipedia.org/wiki/Reverse_proxy">reverse proxy</a> server.</p>
<p>NGINX is not the only web server on the market, though. One of its biggest competitors is <a target="_blank" href="https://httpd.apache.org/">Apache HTTP Server (httpd)</a>, first released back on 1995. In spite of the fact that Apache HTTP Server is more flexible, server admins often prefer NGINX for two main reasons:</p>
<ul>
<li>It can handle a higher number of concurrent requests.</li>
<li>It has faster static content delivery with low resource usage.</li>
</ul>
<p>I won't go further into the whole Apache vs NGINX debate. But if you wish to learn more about the differences between them in detail, this excellent <a target="_blank" href="https://www.digitalocean.com/community/tutorials/apache-vs-nginx-practical-considerations">article</a> from <a target="_blank" href="https://www.digitalocean.com/community/users/jellingwood">Justin Ellingwood</a> may help.</p>
<p>In fact, to explain NGINX's request handling technique, I would like to quote two paragraphs from Justin's article here:</p>
<blockquote>
<p>Nginx came onto the scene after Apache, with more awareness of the concurrency problems that would face sites at scale. Leveraging this knowledge, Nginx was designed from the ground up to use an asynchronous, non-blocking, event-driven connection handling algorithm.  </p>
<p>Nginx spawns worker processes, each of which can handle thousands of connections. The worker processes accomplish this by implementing a fast looping mechanism that continuously checks for and processes events. Decoupling actual work from connections allows each worker to concern itself with a connection only when a new event has been triggered.</p>
</blockquote>
<p>If that seems a bit complicated to understand, don't worry. Having a basic understanding of the inner workings will suffice for now.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/wQszK2rvq-1.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>NGINX is faster in static content delivery while staying relatively lighter on resources because it doesn't embed a dynamic programming language processor. When a request for static content comes, NGINX simply responds with the file without running any additional processes.</p>
<p>That doesn't mean that NGINX can't handle requests that require a dynamic programming language processor. In such cases, NGINX simply delegates the tasks to separate processes such as <a target="_blank" href="https://www.php.net/manual/en/install.fpm.php">PHP-FPM</a>, <a target="_blank" href="https://nodejs.org/">Node.js</a> or <a target="_blank" href="https://python.org/">Python</a>. Then, once that process finishes its work, NGINX reverse proxies the response back to the client.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/_nT7rcdjG.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>NGINX is also a lot easier to configure thanks to a configuration file syntax inspired from various scripting languages that results in compact, easily maintainable configuration files.</p>
<h2 id="heading-how-to-install-nginx">How to Install NGINX</h2>
<p>Installing NGINX on a <a target="_blank" href="https://en.wikipedia.org/wiki/Linux">Linux</a>-based system is pretty straightforward. You can either use a virtual private server running <a target="_blank" href="https://ubuntu.com/">Ubuntu</a> as your playground, or you can provision a virtual machine on your local system using Vagrant.</p>
<p>For the most part, provisioning a local virtual machine will suffice and that's the way I'll be using in this article.</p>
<h3 id="heading-how-to-provision-a-local-virtual-machine">How to Provision a Local Virtual Machine</h3>
<p>For those who doesn't know, <a target="_blank" href="https://vagrantup.com/">Vagrant</a> is an open-source tool by <a target="_blank" href="https://www.hashicorp.com/">Hashicorp</a> that allows you to provision virtual machines using simple configuration files.</p>
<p>For this approach to work, you'll need <a target="_blank" href="https://www.virtualbox.org/wiki/Downloads/">VirtualBox</a> and <a target="_blank" href="https://www.vagrantup.com/downloads/">Vagrant</a>, so go ahead and install them first. If you need a little warm up on the topic, this <a target="_blank" href="https://learn.hashicorp.com/collections/vagrant/getting-started/">tutorial</a> may help.</p>
<p>Create a working directory somewhere in your system with a sensible name. Mine is <code>~/vagrant/nginx-handbook</code> directory.</p>
<p>Inside the working directory create a file named <code>Vagrantfile</code> and put following content in there:</p>
<pre><code class="lang-vagrantfile">Vagrant.configure("2") do |config|

    config.vm.hostname = "nginx-handbook-box"

    config.vm.box = "ubuntu/focal64"

    config.vm.define "nginx-handbook-box"

    config.vm.network "private_network", ip: "192.168.20.20"

    config.vm.provider "virtualbox" do |vb|
      vb.cpus = 1
      vb.memory = "1024"
      vb.name = "nginx-handbook"
    end

  end
</code></pre>
<p>This <code>Vagrantfile</code> is the configuration file I talked about earlier. It contains information like name of the virtual machine, number of CPUs, size of RAM, the IP address, and more.</p>
<p>To start a virtual machine using this configuration, open your terminal inside the working directory and execute the following command:</p>
<pre><code class="lang-shell">vagrant up

# Bringing machine 'nginx-handbook-box' up with 'virtualbox' provider...
# ==&gt; nginx-handbook-box: Importing base box 'ubuntu/focal64'...
# ==&gt; nginx-handbook-box: Matching MAC address for NAT networking...
# ==&gt; nginx-handbook-box: Checking if box 'ubuntu/focal64' version '20210415.0.0' is up to date...
# ==&gt; nginx-handbook-box: Setting the name of the VM: nginx-handbook
# ==&gt; nginx-handbook-box: Clearing any previously set network interfaces...
# ==&gt; nginx-handbook-box: Preparing network interfaces based on configuration...
#     nginx-handbook-box: Adapter 1: nat
#     nginx-handbook-box: Adapter 2: hostonly
# ==&gt; nginx-handbook-box: Forwarding ports...
#     nginx-handbook-box: 22 (guest) =&gt; 2222 (host) (adapter 1)
# ==&gt; nginx-handbook-box: Running 'pre-boot' VM customizations...
# ==&gt; nginx-handbook-box: Booting VM...
# ==&gt; nginx-handbook-box: Waiting for machine to boot. This may take a few minutes...
#     nginx-handbook-box: SSH address: 127.0.0.1:2222
#     nginx-handbook-box: SSH username: vagrant
#     nginx-handbook-box: SSH auth method: private key
#     nginx-handbook-box: Warning: Remote connection disconnect. Retrying...
#     nginx-handbook-box: Warning: Connection reset. Retrying...
#     nginx-handbook-box: 
#     nginx-handbook-box: Vagrant insecure key detected. Vagrant will automatically replace
#     nginx-handbook-box: this with a newly generated keypair for better security.
#     nginx-handbook-box: 
#     nginx-handbook-box: Inserting generated public key within guest...
#     nginx-handbook-box: Removing insecure key from the guest if it's present...
#     nginx-handbook-box: Key inserted! Disconnecting and reconnecting using new SSH key...
# ==&gt; nginx-handbook-box: Machine booted and ready!
# ==&gt; nginx-handbook-box: Checking for guest additions in VM...
# ==&gt; nginx-handbook-box: Setting hostname...
# ==&gt; nginx-handbook-box: Configuring and enabling network interfaces...
# ==&gt; nginx-handbook-box: Mounting shared folders...
#     nginx-handbook-box: /vagrant =&gt; /home/fhsinchy/vagrant/nginx-handbook

vagrant status

# Current machine states:

# nginx-handbook-box           running (virtualbox)
</code></pre>
<p>The output of the <code>vagrant up</code> command may differ on your system, but as long as <code>vagrant status</code> says the machine is running, you're good to go. </p>
<p>Given that the virtual machine is now running, you should be able to SSH into it. To do so, execute the following command:</p>
<pre><code class="lang-shell">vagrant ssh nginx-handbook-box

# Welcome to Ubuntu 20.04.2 LTS (GNU/Linux 5.4.0-72-generic x86_64)
# vagrant@nginx-handbook-box:~$
</code></pre>
<p>If everything's done correctly you should be logged into your virtual machine, which will be evident by the <code>vagrant@nginx-handbook-box</code> line on your terminal. </p>
<p>This virtual machine will be accessible on <strong>http://192.168.20.20</strong> on your local machine. You can even assign a custom domain like <strong>http://nginx-handbook.test</strong> to the virtual machine by adding an entry to your <strong>hosts</strong> file:</p>
<pre><code class="lang-shell"># on mac and linux terminal
sudo nano /etc/hosts

# on windows command prompt as administrator
notepad c:\windows\system32\drivers\etc\hosts
</code></pre>
<p>Now append the following line at the end of the file:</p>
<pre><code><span class="hljs-number">192.168</span><span class="hljs-number">.20</span><span class="hljs-number">.20</span>   nginx-handbook.test
</code></pre><p>Now you should be able to access the virtual machine on <strong>http://nginx-handbook.test</strong> URI in your browser.</p>
<p>You can stop or destroy the virtual machine by executing the following commands inside the working directory:</p>
<pre><code class="lang-shell"># to stop the virtual machine
vagrant halt

# to destroy the virtual machine
vagrant destroy
</code></pre>
<p>If you want to learn about more Vagrant commands, this <a target="_blank" href="https://gist.github.com/wpscholar/a49594e2e2b918f4d0c4">cheat sheet</a> may come in handy.</p>
<p>Now that you have a functioning Ubuntu virtual machine on your system, all that is left to do is <a class="post-section-overview" href="#heading-how-to-install-nginx-on-a-provisioned-server-or-virtual-machine-2">install NGINX</a>.</p>
<h3 id="heading-how-to-provision-a-virtual-private-server">How to Provision a Virtual Private Server</h3>
<p>For this demonstration, I'll use <a target="_blank" href="https://vultr.com/">Vultr</a> as my provider but you may use <a target="_blank" href="https://digitalocean.com/">DigitalOcean</a> or whatever provider you like.</p>
<p>Assuming you already have an account with your provider, log into the account and deploy a new server:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/ZUAu_Tpxx-2.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>On DigitalOcean, it's usually called a droplet. On the next screen, choose a location close to you. I live in Bangladesh which is why I've chosen Singapore:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/zH08EnmGq.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>On the next step, you'll have to choose the operating system and server size. Choose Ubuntu 20.04 and the smallest possible server size:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/G8mEC13pp.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Although production servers tend to be much bigger and more powerful than this, a tiny server will be more than enough for this article.</p>
<p>Finally, for the last step, put something fitting like <strong>nginx-hadnbook-demo-server</strong> as the server host and label. You can even leave them empty if you want. </p>
<p>Once you're happy with your choices, go ahead and press the <strong>Deploy Now</strong> button.</p>
<p>The deployment process may take some time to finish, but once it's done, you'll see the newly created server on your dashboard:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/server-list.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Also pay attention to the <strong>Status –</strong> it should say <strong>Running</strong> and not <strong>Preparing</strong> or <strong>Stopped</strong>. To connect to the server, you'll need a username and password.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/server-overview.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Go into the overview page for your server and there you should see the server's IP address, username, and password:</p>
<p>The generic command for logging into a server using SSH is as follows:</p>
<pre><code class="lang-shell">ssh &lt;username&gt;@&lt;ip address&gt;
</code></pre>
<p>So in the case of my server, it'll be:</p>
<pre><code class="lang-shell">ssh root@45.77.251.108

# Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
# Warning: Permanently added '45.77.251.108' (ECDSA) to the list of known hosts.

# root@45.77.251.108's password: 
# Welcome to Ubuntu 20.04.2 LTS (GNU/Linux 5.4.0-65-generic x86_64)

# root@localhost:~#
</code></pre>
<p>You'll be asked if you want to continue connecting to this server or not. Answer with <code>yes</code> and then you'll be asked for the password. Copy the password from the server overview page and paste that into your terminal. </p>
<p>If you do everything correctly you should be logged into your server – you'll see the <code>root@localhost</code> line on your terminal. Here <code>localhost</code> is the server host name, and may differ in your case.</p>
<p>You can access this server directly by its IP address. Or if you own any custom domain, you can use that also. </p>
<p>Throughout the article you'll see me adding test domains to my operating system's <code>hosts</code> file. In case of a real server, you'll have to configure those servers using your DNS provider.</p>
<p>Remember that you'll be charged as long as this server is being used. Although the charge should be very small, I'm warning you anyways. You can destroy the server anytime you want by hitting the trash icon on the server overview page:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/image-90.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>If you own a custom domain name, you may assign a sub-domain to this server. Now that you're inside the server, all that is left to is <a class="post-section-overview" href="#heading-how-to-install-nginx-on-a-provisioned-server-or-virtual-machine-2">install NGINX</a>.</p>
<h3 id="heading-how-to-install-nginx-on-a-provisioned-server-or-virtual-machine">How to Install NGINX on a Provisioned Server or Virtual Machine</h3>
<p>Assuming you're logged into your server or virtual machine, the first thing you should do is performing an update. Execute the following command to do so:</p>
<pre><code class="lang-shell">sudo apt update &amp;&amp; sudo apt upgrade -y
</code></pre>
<p>After the update, install NGINX by executing the following command:</p>
<pre><code class="lang-shell">sudo apt install nginx -y
</code></pre>
<p>Once the installation is done, NGINX should be automatically registered as a <code>systemd</code> service and should be running. To check, execute the following command:</p>
<pre><code class="lang-shell">sudo systemctl status nginx

# ● nginx.service - A high performance web server and a reverse proxy server
#      Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)
#      Active: active (running)
</code></pre>
<p>If the status says <code>running</code>, then you're good to go. Otherwise you may start the service by executing this command:</p>
<pre><code class="lang-shell">sudo systemctl start nginx
</code></pre>
<p>Finally for a visual verification that everything is working properly, visit your server/virtual machine with your favorite browser and you should see NGINX's default welcome page:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/image-89.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>NGINX is usually installed on the <code>/etc/nginx</code> directory and the majority of our work in the upcoming sections will be done in here.</p>
<p>Congratulations! Bow you have NGINX up and running on your server/virtual machine. Now it's time to jump head first into NGINX.</p>
<h2 id="heading-introduction-to-nginxs-configuration-files">Introduction to NGINX's Configuration Files</h2>
<p>As a web server, NGINX's job is to serve static or dynamic contents to the clients. But how that content are going to be served is usually controlled by configuration files.</p>
<p>NGINX's configuration files end with the <code>.conf</code> extension and usually live inside the <code>/etc/nginx/</code> directory. Let's begin by <code>cd</code>ing into this directory and getting a list of all the files:</p>
<pre><code class="lang-shell">cd /etc/nginx

ls -lh

# drwxr-xr-x 2 root root 4.0K Apr 21  2020 conf.d
# -rw-r--r-- 1 root root 1.1K Feb  4  2019 fastcgi.conf
# -rw-r--r-- 1 root root 1007 Feb  4  2019 fastcgi_params
# -rw-r--r-- 1 root root 2.8K Feb  4  2019 koi-utf
# -rw-r--r-- 1 root root 2.2K Feb  4  2019 koi-win
# -rw-r--r-- 1 root root 3.9K Feb  4  2019 mime.types
# drwxr-xr-x 2 root root 4.0K Apr 21  2020 modules-available
# drwxr-xr-x 2 root root 4.0K Apr 17 14:42 modules-enabled
# -rw-r--r-- 1 root root 1.5K Feb  4  2019 nginx.conf
# -rw-r--r-- 1 root root  180 Feb  4  2019 proxy_params
# -rw-r--r-- 1 root root  636 Feb  4  2019 scgi_params
# drwxr-xr-x 2 root root 4.0K Apr 17 14:42 sites-available
# drwxr-xr-x 2 root root 4.0K Apr 17 14:42 sites-enabled
# drwxr-xr-x 2 root root 4.0K Apr 17 14:42 snippets
# -rw-r--r-- 1 root root  664 Feb  4  2019 uwsgi_params
# -rw-r--r-- 1 root root 3.0K Feb  4  2019 win-utf
</code></pre>
<p>Among these files, there should be one named <strong>nginx.conf</strong>. This is the the main configuration file for NGINX. You can have a look at the content of this file using the <code>cat</code> program:</p>
<pre><code class="lang-shell">cat nginx.conf

# user www-data;
# worker_processes auto;
# pid /run/nginx.pid;
# include /etc/nginx/modules-enabled/*.conf;

# events {
#     worker_connections 768;
#     # multi_accept on;
# }

# http {

#     ##
#     # Basic Settings
#     ##

#     sendfile on;
#     tcp_nopush on;
#     tcp_nodelay on;
#     keepalive_timeout 65;
#     types_hash_max_size 2048;
#     # server_tokens off;

#     # server_names_hash_bucket_size 64;
#     # server_name_in_redirect off;

#     include /etc/nginx/mime.types;
#     default_type application/octet-stream;

#     ##
#     # SSL Settings
#     ##

#     ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE
#     ssl_prefer_server_ciphers on;

#     ##
#     # Logging Settings
#     ##

#     access_log /var/log/nginx/access.log;
#     error_log /var/log/nginx/error.log;

#     ##
#     # Gzip Settings
#     ##

#     gzip on;

#     # gzip_vary on;
#     # gzip_proxied any;
#     # gzip_comp_level 6;
#     # gzip_buffers 16 8k;
#     # gzip_http_version 1.1;
#     # gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

#     ##
#     # Virtual Host Configs
#     ##

#     include /etc/nginx/conf.d/*.conf;
#     include /etc/nginx/sites-enabled/*;
# }


# #mail {
# #    # See sample authentication script at:
# #    # http://wiki.nginx.org/ImapAuthenticateWithApachePhpScript
# # 
# #    # auth_http localhost/auth.php;
# #    # pop3_capabilities "TOP" "USER";
# #    # imap_capabilities "IMAP4rev1" "UIDPLUS";
# # 
# #    server {
# #        listen     localhost:110;
# #        protocol   pop3;
# #        proxy      on;
# #    }
# # 
# #    server {
# #        listen     localhost:143;
# #        protocol   imap;
# #        proxy      on;
# #    }
# #}
</code></pre>
<p>Whoa! That's a lot of stuff. Trying to understand this file at its current state will be a nightmare. So let's rename the file and create a new empty one:</p>
<pre><code class="lang-shell"># renames the file
sudo mv nginx.conf nginx.conf.backup

# creates a new file
sudo touch nginx.conf
</code></pre>
<p>I <strong>highly discourage</strong> you from editing the original <code>nginx.conf</code> file unless you absolutely know what you're doing. For learning purposes, you may rename it, but <a class="post-section-overview" href="#heading-how-to-understand-the-main-configuration-file-1">later on</a>, I'll show you how you should go about configuring a server in a real life scenario.</p>
<h2 id="heading-how-to-configure-a-basic-web-server">How to Configure a Basic Web Server</h2>
<p>In this section of the book, you'll finally get your hands dirty by configuring a basic static web server from the ground up. The goal of this section is to introduce you to the syntax and fundamental concepts of NGINX configuration files.</p>
<h3 id="heading-how-to-write-your-first-configuration-file">How to Write Your First Configuration File</h3>
<p>Start by opening the newly created <code>nginx.conf</code> file using the <a target="_blank" href="https://www.nano-editor.org/">nano</a> text editor:</p>
<pre><code class="lang-shell">sudo nano /etc/nginx/nginx.conf
</code></pre>
<p>Throughout the book, I'll be using nano as my text editor. You may use something more modern if you want to, but in a real life scenario, you're most likely to work using <a target="_blank" href="https://www.nano-editor.org/">nano</a> or <a target="_blank" href="https://www.vim.org/">vim</a> on servers instead of anything else. So use this book as an opportunity to sharpen your nano skills. Also the official <a target="_blank" href="https://www.nano-editor.org/dist/latest/cheatsheet.html">cheat sheet</a> is there for you to consult whenever you need.</p>
<p>After opening the file, update its content to look like this:</p>
<pre><code class="lang-conf">events {

}

http {

    server {

        listen 80;
        server_name nginx-handbook.test;

        return 200 "Bonjour, mon ami!\n";
    }

}
</code></pre>
<p>If you have experience building REST APIs then you may guess from the <code>return 200 "Bonjour, mon ami!\n";</code> line that the server has been configured to respond with a status code of 200 and the message "Bonjour, mon ami!".</p>
<p>Don't worry if you don't understand anything more than that at the moment. I'll explain this file line by line, but first let's see this configuration in action.</p>
<h3 id="heading-how-to-validate-and-reload-configuration-files">How to Validate and Reload Configuration Files</h3>
<p>After writing a new configuration file or updating an old one, the first thing to do is check the file for any syntax mistakes. The <code>nginx</code> binary includes an option <code>-t</code> to do just that.</p>
<pre><code class="lang-shell">sudo nginx -t

# nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
# nginx: configuration file /etc/nginx/nginx.conf test is successful
</code></pre>
<p>If you have any syntax errors, this command will let you know about them, including the line number. </p>
<p>Although the configuration file is fine, NGINX will not use it. The way NGINX works is it reads the configuration file once and keeps working based on that.</p>
<p>If you update the configuration file, then you'll have to instruct NGINX explicitly to reload the configuration file. There are two ways to do that.</p>
<ul>
<li>You can restart the NGINX service by executing the <code>sudo systemctl restart nginx</code> command.</li>
<li>You can dispatch a <code>reload</code> signal to NGINX by executing the <code>sudo nginx -s reload</code> command.</li>
</ul>
<p>The <code>-s</code> option is used for dispatching various signals to NGINX. The available signals are <code>stop</code>, <code>quit</code>, <code>reload</code> and <code>reopen</code>. Among the two ways I just mentioned, I prefer the second one simply because it's less typing.</p>
<p>Once you've reloaded the configuration file by executing the <code>nginx -s reload</code> command, you can see it in action by sending a simple get request to the server:</p>
<pre><code class="lang-shell">curl -i http://nginx-handbook.test

# HTTP/1.1 200 OK
# Server: nginx/1.18.0 (Ubuntu)
# Date: Wed, 21 Apr 2021 10:03:33 GMT
# Content-Type: text/plain
# Content-Length: 18
# Connection: keep-alive

# Bonjour, mon ami!
</code></pre>
<p>The server is responding with a status code of 200 and the expected message. Congratulations on getting this far! Now it's time for some explanation.</p>
<h3 id="heading-how-to-understand-directives-and-contexts-in-nginx">How to Understand Directives and Contexts in NGINX</h3>
<p>The few lines of code you've written here, although seemingly simple, introduce two of the most important terminologies of NGINX configuration files. They are <strong>directives</strong> and <strong>contexts</strong>.</p>
<p>Technically, everything inside a NGINX configuration file is a <strong>directive</strong>. Directives are of two types:</p>
<ul>
<li>Simple Directives</li>
<li>Block Directives</li>
</ul>
<p>A simple directive consists of the directive name and the space delimited parameters, like <code>listen</code>, <code>return</code> and others. Simple directives are terminated by semicolons.</p>
<p>Block directives are similar to simple directives, except that instead of ending with semicolons, they end with a pair of curly braces <code>{ }</code> enclosing additional instructions. </p>
<p>A block directive capable of containing other directives inside it is called a context, that is <code>events</code>, <code>http</code> and so on. There are four core contexts in NGINX:</p>
<ul>
<li><code>events { }</code> – The <code>events</code> context is used for setting global configuration regarding how NGINX is going to handle requests on a general level. There can be only one <code>events</code> context in a valid configuration file.</li>
<li><code>http { }</code> – Evident by the name, <code>http</code> context is used for defining configuration regarding how the server is going to handle HTTP and HTTPS requests, specifically. There can be only one <code>http</code> context in a valid configuration file.</li>
<li><code>server { }</code> – The <code>server</code> context is nested inside the <code>http</code> context and used for configuring specific virtual servers within a single host. There can be multiple <code>server</code> contexts in a valid configuration file nested inside the <code>http</code> context. Each <code>server</code> context is considered a virtual host.</li>
<li><code>main</code> – The <code>main</code> context is the configuration file itself. Anything written outside of the three previously mentioned contexts is on the <code>main</code> context.</li>
</ul>
<p>You can treat contexts in NGINX like scopes in other programming languages. There is also a sense of inheritance among them. You can find an <a target="_blank" href="https://nginx.org/en/docs/dirindex.html">alphabetical index of directives</a> on the official NGINX docs.</p>
<p>I've already mentioned that there can be multiple <code>server</code> contexts within a configuration file. But when a request reaches the server, how does NGINX know which one of those contexts should handle the request?</p>
<p>The <code>listen</code> directive is one of the ways to identify the correct <code>server</code> context within a configuration. Consider the following scenario:</p>
<pre><code>http {
    server {
        listen <span class="hljs-number">80</span>;
        server_name nginx-handbook.test;

        <span class="hljs-keyword">return</span> <span class="hljs-number">200</span> <span class="hljs-string">"hello from port 80!\n"</span>;
    }


    server {
        listen <span class="hljs-number">8080</span>;
        server_name nginx-handbook.test;

        <span class="hljs-keyword">return</span> <span class="hljs-number">200</span> <span class="hljs-string">"hello from port 8080!\n"</span>;
    }
}
</code></pre><p>Now if you send a request to http://nginx-handbook.test:80 then you'll receive "hello from port 80!" as a response. And if you send a request to http://nginx-handbook.test:8080, you'll receive "hello from port 8080!" as a response:</p>
<pre><code>curl nginx-handbook.test:<span class="hljs-number">80</span>

# hello <span class="hljs-keyword">from</span> port <span class="hljs-number">80</span>!

curl nginx-handbook.test:<span class="hljs-number">8080</span>

# hello <span class="hljs-keyword">from</span> port <span class="hljs-number">8080</span>!
</code></pre><p>These two server blocks are like two people holding telephone receivers, waiting to respond when a request reaches one of their numbers. Their numbers are indicated by the <code>listen</code> directives.</p>
<p>Apart from the <code>listen</code> directive, there is also the <code>server_name</code> directive. Consider the following scenario of an imaginary library management application:</p>
<pre><code>http {
    server {
        listen <span class="hljs-number">80</span>;
        server_name library.test;

        <span class="hljs-keyword">return</span> <span class="hljs-number">200</span> <span class="hljs-string">"your local library!\n"</span>;
    }


    server {
        listen <span class="hljs-number">80</span>;
        server_name librarian.library.test;

        <span class="hljs-keyword">return</span> <span class="hljs-number">200</span> <span class="hljs-string">"welcome dear librarian!\n"</span>;
    }
}
</code></pre><p>This is a basic example of the idea of virtual hosts. You're running two separate applications under different server names in the same server.</p>
<p>If you send a request to http://library.test then you'll get "your local library!" as a response. If you send a request to http://librarian.library.test, you'll get "welcome dear librarian!" as a response.</p>
<pre><code class="lang-shell">curl http://library.test

# your local library!

curl http://librarian.library.test

# welcome dear librarian!
</code></pre>
<p>To make this demo work on your system, you'll have to update your <code>hosts</code> file to include these two domain names as well:</p>
<pre><code class="lang-hosts">192.168.20.20   library.test
192.168.20.20   librarian.library.test
</code></pre>
<p>Finally, the <code>return</code> directive is responsible for returning a valid response to the user. This directive takes two parameters: the status code and the string message to be returned.</p>
<h3 id="heading-how-to-serve-static-content-using-nginx">How to Serve Static Content Using NGINX</h3>
<p>Now that you have a good understanding of how to write a basic configuration file for NGINX, let's upgrade the configuration to serve static files instead of plain text responses.</p>
<p>In order to serve static content, you first have to store them somewhere on your server. If you list the files and directory on the root of your server using <code>ls</code>, you'll find a directory called <code>/srv</code> in there:</p>
<pre><code class="lang-shell">ls -lh /

# lrwxrwxrwx   1 root    root       7 Apr 16 02:10 bin -&gt; usr/bin
# drwxr-xr-x   3 root    root    4.0K Apr 16 02:13 boot
# drwxr-xr-x  16 root    root    3.8K Apr 21 09:23 dev
# drwxr-xr-x  92 root    root    4.0K Apr 21 09:24 etc
# drwxr-xr-x   4 root    root    4.0K Apr 21 08:04 home
# lrwxrwxrwx   1 root    root       7 Apr 16 02:10 lib -&gt; usr/lib
# lrwxrwxrwx   1 root    root       9 Apr 16 02:10 lib32 -&gt; usr/lib32
# lrwxrwxrwx   1 root    root       9 Apr 16 02:10 lib64 -&gt; usr/lib64
# lrwxrwxrwx   1 root    root      10 Apr 16 02:10 libx32 -&gt; usr/libx32
# drwx------   2 root    root     16K Apr 16 02:15 lost+found
# drwxr-xr-x   2 root    root    4.0K Apr 16 02:10 media
# drwxr-xr-x   2 root    root    4.0K Apr 16 02:10 mnt
# drwxr-xr-x   2 root    root    4.0K Apr 16 02:10 opt
# dr-xr-xr-x 152 root    root       0 Apr 21 09:23 proc
# drwx------   5 root    root    4.0K Apr 21 09:59 root
# drwxr-xr-x  26 root    root     820 Apr 21 09:47 run
# lrwxrwxrwx   1 root    root       8 Apr 16 02:10 sbin -&gt; usr/sbin
# drwxr-xr-x   6 root    root    4.0K Apr 16 02:14 snap
# drwxr-xr-x   2 root    root    4.0K Apr 16 02:10 srv
# dr-xr-xr-x  13 root    root       0 Apr 21 09:23 sys
# drwxrwxrwt  11 root    root    4.0K Apr 21 09:24 tmp
# drwxr-xr-x  15 root    root    4.0K Apr 16 02:12 usr
# drwxr-xr-x   1 vagrant vagrant   38 Apr 21 09:23 vagrant
# drwxr-xr-x  14 root    root    4.0K Apr 21 08:34 var
</code></pre>
<p>This <code>/srv</code> directory is meant to contain site-specific data which is served by this system. Now <code>cd</code> into this directory and clone the code repository that comes with this book:</p>
<pre><code>cd /srv

sudo git clone https:<span class="hljs-comment">//github.com/fhsinchy/nginx-handbook-projects.git</span>
</code></pre><p>Inside the <code>nginx-handbook-projects</code> directory there should a directory called <code>static-demo</code> containing four files in total:</p>
<pre><code class="lang-shell">ls -lh /srv/nginx-handbook-projects/static-demo

# -rw-r--r-- 1 root root 960 Apr 21 11:27 about.html
# -rw-r--r-- 1 root root 960 Apr 21 11:27 index.html
# -rw-r--r-- 1 root root 46K Apr 21 11:27 mini.min.css
# -rw-r--r-- 1 root root 19K Apr 21 11:27 the-nginx-handbook.jpg
</code></pre>
<p>Now that you have the static content to be served, update your configuration as follows:</p>
<pre><code class="lang-conf">events {

}

http {

    server {

        listen 80;
        server_name nginx-handbook.test;

        root /srv/nginx-handbook-projects/static-demo;
    }

}
</code></pre>
<p>The code is almost the same, except the <code>return</code> directive has now been replaced by a <code>root</code> directive. This directive is used for declaring the root directory for a site. </p>
<p>By writing <code>root /srv/nginx-handbook-projects/static-demo</code> you're telling NGINX to look for files to serve inside the <code>/srv/nginx-handbook-projects/static-demo</code> directory if any request comes to this server. Since NGINX is a web server, it is smart enough to serve the <code>index.html</code> file by default.</p>
<p>Let's see if this works or not. Test and reload the updated configuration file and visit the server. You should be greeted with a somewhat broken HTML site:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/image-91.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Although NGINX has served the index.html file correctly, judging by the look of the three navigation links, it seems like the CSS code is not working.</p>
<p>You may think that there is something wrong in the CSS file. But in reality, the problem is in the configuration file.</p>
<h3 id="heading-static-file-type-handling-in-nginx">Static File Type Handling in NGINX</h3>
<p>To debug the issue you're facing right now, send a request for the CSS file to the server:</p>
<pre><code class="lang-shell">curl -I http://nginx-handbook/mini.min.css

# HTTP/1.1 200 OK
# Server: nginx/1.18.0 (Ubuntu)
# Date: Wed, 21 Apr 2021 12:17:16 GMT
# Content-Type: text/plain
# Content-Length: 46887
# Last-Modified: Wed, 21 Apr 2021 11:27:06 GMT
# Connection: keep-alive
# ETag: "60800c0a-b727"
# Accept-Ranges: bytes
</code></pre>
<p>Pay attention to the <strong>Content-Type</strong> and see how it says <strong>text/plain</strong> and not <strong>text/css</strong>. This means that NGINX is serving this file as plain text instead of as a stylesheet.</p>
<p>Although NGINX is smart enough to find the <code>index.html</code> file by default, it's pretty dumb when it comes to interpreting file types. To solve this problem update your configuration once again:</p>
<pre><code class="lang-conf">events {

}

http {

    types {
        text/html html;
        text/css css;
    }

    server {

        listen 80;
        server_name nginx-handbook.test;

        root /srv/nginx-handbook-projects/static-demo;
    }
}
</code></pre>
<p>The only change we've made to the code is a new <code>types</code> context nested inside the <code>http</code> block. As you may have already guessed from the name, this context is used for configuring file types.</p>
<p>By writing <code>text/html html</code> in this context you're telling NGINX to parse any file as <code>text/html</code> that ends with the <code>html</code> extension.</p>
<p>You may think that configuring the CSS file type should suffice as the HTML is being parsed just fine – but no. </p>
<p>If you introduce a <code>types</code> context in the configuration, NGINX becomes even dumber and only parses the files configured by you. So if you only define the <code>text/css css</code> in this context then NGINX will start parsing the HTML file as plain text.</p>
<p>Validate and reload the newly updated config file and visit the server once again. Send a request for the CSS file once again, and this time the file should be parsed as a <strong>text/css</strong> file:</p>
<pre><code class="lang-shell">curl -I http://nginx-handbook.test/mini.min.css

# HTTP/1.1 200 OK
# Server: nginx/1.18.0 (Ubuntu)
# Date: Wed, 21 Apr 2021 12:29:35 GMT
# Content-Type: text/css
# Content-Length: 46887
# Last-Modified: Wed, 21 Apr 2021 11:27:06 GMT
# Connection: keep-alive
# ETag: "60800c0a-b727"
# Accept-Ranges: bytes
</code></pre>
<p>Visit the server for a visual verification, and the site should look better this time:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/image-92.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>If you've updated and reloaded the configuration file correctly and you're still seeing the old site, perform a hard refresh.</p>
<h3 id="heading-how-to-include-partial-config-files">How to Include Partial Config Files</h3>
<p>Mapping file types within the <code>types</code> context may work for small projects, but for bigger projects it can be cumbersome and error-prone.</p>
<p>NGINX provides a solution for this problem. If you list the files inside the <code>/etc/nginx</code> directory once again, you'll see a file named <code>mime.types</code>.</p>
<pre><code class="lang-shell">ls -lh /etc/nginx

# drwxr-xr-x 2 root root 4.0K Apr 21  2020 conf.d
# -rw-r--r-- 1 root root 1.1K Feb  4  2019 fastcgi.conf
# -rw-r--r-- 1 root root 1007 Feb  4  2019 fastcgi_params
# -rw-r--r-- 1 root root 2.8K Feb  4  2019 koi-utf
# -rw-r--r-- 1 root root 2.2K Feb  4  2019 koi-win
# -rw-r--r-- 1 root root 3.9K Feb  4  2019 mime.types
# drwxr-xr-x 2 root root 4.0K Apr 21  2020 modules-available
# drwxr-xr-x 2 root root 4.0K Apr 17 14:42 modules-enabled
# -rw-r--r-- 1 root root 1.5K Feb  4  2019 nginx.conf
# -rw-r--r-- 1 root root  180 Feb  4  2019 proxy_params
# -rw-r--r-- 1 root root  636 Feb  4  2019 scgi_params
# drwxr-xr-x 2 root root 4.0K Apr 17 14:42 sites-available
# drwxr-xr-x 2 root root 4.0K Apr 17 14:42 sites-enabled
# drwxr-xr-x 2 root root 4.0K Apr 17 14:42 snippets
# -rw-r--r-- 1 root root  664 Feb  4  2019 uwsgi_params
# -rw-r--r-- 1 root root 3.0K Feb  4  2019 win-utf
</code></pre>
<p>Let's have a look at the content of this file:</p>
<pre><code class="lang-shell">cat /etc/mime.types

# types {
#     text/html                             html htm shtml;
#     text/css                              css;
#     text/xml                              xml;
#     image/gif                             gif;
#     image/jpeg                            jpeg jpg;
#     application/javascript                js;
#     application/atom+xml                  atom;
#     application/rss+xml                   rss;

#     text/mathml                           mml;
#     text/plain                            txt;
#     text/vnd.sun.j2me.app-descriptor      jad;
#     text/vnd.wap.wml                      wml;
#     text/x-component                      htc;

#     image/png                             png;
#     image/tiff                            tif tiff;
#     image/vnd.wap.wbmp                    wbmp;
#     image/x-icon                          ico;
#     image/x-jng                           jng;
#     image/x-ms-bmp                        bmp;
#     image/svg+xml                         svg svgz;
#     image/webp                            webp;

#     application/font-woff                 woff;
#     application/java-archive              jar war ear;
#     application/json                      json;
#     application/mac-binhex40              hqx;
#     application/msword                    doc;
#     application/pdf                       pdf;
#     application/postscript                ps eps ai;
#     application/rtf                       rtf;
#     application/vnd.apple.mpegurl         m3u8;
#     application/vnd.ms-excel              xls;
#     application/vnd.ms-fontobject         eot;
#     application/vnd.ms-powerpoint         ppt;
#     application/vnd.wap.wmlc              wmlc;
#     application/vnd.google-earth.kml+xml  kml;
#     application/vnd.google-earth.kmz      kmz;
#     application/x-7z-compressed           7z;
#     application/x-cocoa                   cco;
#     application/x-java-archive-diff       jardiff;
#     application/x-java-jnlp-file          jnlp;
#     application/x-makeself                run;
#     application/x-perl                    pl pm;
#     application/x-pilot                   prc pdb;
#     application/x-rar-compressed          rar;
#     application/x-redhat-package-manager  rpm;
#     application/x-sea                     sea;
#     application/x-shockwave-flash         swf;
#     application/x-stuffit                 sit;
#     application/x-tcl                     tcl tk;
#     application/x-x509-ca-cert            der pem crt;
#     application/x-xpinstall               xpi;
#     application/xhtml+xml                 xhtml;
#     application/xspf+xml                  xspf;
#     application/zip                       zip;

#     application/octet-stream              bin exe dll;
#     application/octet-stream              deb;
#     application/octet-stream              dmg;
#     application/octet-stream              iso img;
#     application/octet-stream              msi msp msm;

#     application/vnd.openxmlformats-officedocument.wordprocessingml.document    docx;
#     application/vnd.openxmlformats-officedocument.spreadsheetml.sheet          xlsx;
#     application/vnd.openxmlformats-officedocument.presentationml.presentation  pptx;

#     audio/midi                            mid midi kar;
#     audio/mpeg                            mp3;
#     audio/ogg                             ogg;
#     audio/x-m4a                           m4a;
#     audio/x-realaudio                     ra;

#     video/3gpp                            3gpp 3gp;
#     video/mp2t                            ts;
#     video/mp4                             mp4;
#     video/mpeg                            mpeg mpg;
#     video/quicktime                       mov;
#     video/webm                            webm;
#     video/x-flv                           flv;
#     video/x-m4v                           m4v;
#     video/x-mng                           mng;
#     video/x-ms-asf                        asx asf;
#     video/x-ms-wmv                        wmv;
#     video/x-msvideo                       avi;
# }
</code></pre>
<p>The file contains a long list of file types and their extensions. To use this file inside your configuration file, update your configuration to look as follows:</p>
<pre><code class="lang-conf">events {

}

http {

    include /etc/nginx/mime.types;

    server {

        listen 80;
        server_name nginx-handbook.test;

        root /srv/nginx-handbook-projects/static-demo;
    }

}
</code></pre>
<p>The old <code>types</code> context has now been replaced with a new <code>include</code> directive. Like the name suggests, this directive allows you to include content from other configuration files.</p>
<p>Validate and reload the configuration file and send a request for the <code>mini.min.css</code> file once again:</p>
<pre><code class="lang-shell">curl -I http://nginx-handbook.test/mini.min.css

# HTTP/1.1 200 OK
# Server: nginx/1.18.0 (Ubuntu)
# Date: Wed, 21 Apr 2021 12:29:35 GMT
# Content-Type: text/css
# Content-Length: 46887
# Last-Modified: Wed, 21 Apr 2021 11:27:06 GMT
# Connection: keep-alive
# ETag: "60800c0a-b727"
# Accept-Ranges: bytes
</code></pre>
<p>In the section below on how to understand the main configuration file, I'll demonstrate how <code>include</code> can be used to modularize your virtual server configurations.</p>
<h2 id="heading-dynamic-routing-in-nginx">Dynamic Routing in NGINX</h2>
<p>The configuration you wrote in the previous section was a very simple static content server configuration. All it did was match a file from the site root corresponding to the URI the client visits and respond back.</p>
<p>So if the client requests files existing on the root such as <code>index.html</code>, <code>about.html</code> or <code>mini.min.css</code> NGINX will return the file. But if you visit a route such as http://nginx-handbook.test/nothing, it'll respond with the default 404 page:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/image-93.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>In this section of the book, you'll learn about the <code>location</code> context, variables, redirects, rewrites and the <code>try_files</code> directive. There will be no new projects in this section but the concepts you learn here will be necessary in the upcoming sections.</p>
<p>Also the configuration will change very frequently in this section, so do not forget to validate and reload the configuration file after every update.</p>
<h3 id="heading-location-matches">Location Matches</h3>
<p>The first concept we'll discuss in this section is the <code>location</code> context. Update the configuration as follows:</p>
<pre><code class="lang-conf">events {

}

http {

    server {

        listen 80;
        server_name nginx-handbook.test;

        location /agatha {
            return 200 "Miss Marple.\nHercule Poirot.\n";
        }
    }
}
</code></pre>
<p>We've replaced the <code>root</code> directive with a new <code>location</code> context. This context is usually nested inside <code>server</code> blocks. There can be multiple <code>location</code> contexts within a <code>server</code> context.</p>
<p>If you send a request to http://nginx-handbook.test/agatha, you'll get a 200 response code and list of characters created by <a target="_blank" href="https://en.wikipedia.org/wiki/Agatha_Christie">Agatha Christie</a>.</p>
<pre><code class="lang-shell">curl -i http://nginx-handbook.test/agatha

# HTTP/1.1 200 OK
# Server: nginx/1.18.0 (Ubuntu)
# Date: Wed, 21 Apr 2021 15:59:07 GMT
# Content-Type: text/plain
# Content-Length: 29
# Connection: keep-alive

# Miss Marple.
# Hercule Poirot.
</code></pre>
<p>Now if you send a request to http://nginx-handbook.test/agatha-christie, you'll get the same response:</p>
<pre><code class="lang-shell">curl -i http://nginx-handbook.test/agatha-christie

# HTTP/1.1 200 OK
# Server: nginx/1.18.0 (Ubuntu)
# Date: Wed, 21 Apr 2021 15:59:07 GMT
# Content-Type: text/plain
# Content-Length: 29
# Connection: keep-alive

# Miss Marple.
# Hercule Poirot.
</code></pre>
<p>This happens because, by writing <code>location /agatha</code>, you're telling NGINX to match any URI starting with "agatha".  This kind of match is called a <strong>prefix match</strong>.</p>
<p>To perform an <strong>exact match</strong>, you'll have to update the code as follows:</p>
<pre><code class="lang-conf">events {

}

http {

    server {

        listen 80;
        server_name nginx-handbook.test;

        location = /agatha {
            return 200 "Miss Marple.\nHercule Poirot.\n";
        }
    }

}
</code></pre>
<p>Adding an <code>=</code> sign before the location URI will instruct NGINX to respond only if the URL matches exactly. Now if you send a request to anything but <code>/agatha</code>, you'll get a 404 response.</p>
<pre><code class="lang-shell">curl -I http://nginx-handbook.test/agatha-christie

# HTTP/1.1 404 Not Found
# Server: nginx/1.18.0 (Ubuntu)
# Date: Wed, 21 Apr 2021 16:14:29 GMT
# Content-Type: text/html
# Content-Length: 162
# Connection: keep-alive

curl -I http://nginx-handbook.test/agatha

# HTTP/1.1 200 OK
# Server: nginx/1.18.0 (Ubuntu)
# Date: Wed, 21 Apr 2021 16:15:04 GMT
# Content-Type: text/plain
# Content-Length: 29
# Connection: keep-alive
</code></pre>
<p>Another kind of match in NGINX is the <strong>regex match</strong>. Using this match you can check location URLs against complex regular expressions.</p>
<pre><code class="lang-conf">events {

}

http {

    server {

        listen 80;
        server_name nginx-handbook.test;

        location ~ /agatha[0-9] {
            return 200 "Miss Marple.\nHercule Poirot.\n";
        }
    }

}
</code></pre>
<p>By replacing the previously used <code>=</code> sign with a <code>~</code> sign, you're telling NGINX to perform a regular expression match. Setting the location to <code>~ /agatha[0-9]</code> means NIGINX will only respond if there is a number after the word "agatha":</p>
<pre><code class="lang-shell">curl -I http://nginx-handbook.test/agatha

# HTTP/1.1 404 Not Found
# Server: nginx/1.18.0 (Ubuntu)
# Date: Wed, 21 Apr 2021 16:14:29 GMT
# Content-Type: text/html
# Content-Length: 162
# Connection: keep-alive

curl -I http://nginx-handbook.test/agatha8

# HTTP/1.1 200 OK
# Server: nginx/1.18.0 (Ubuntu)
# Date: Wed, 21 Apr 2021 16:15:04 GMT
# Content-Type: text/plain
# Content-Length: 29
# Connection: keep-alive
</code></pre>
<p>A regex match is by default case sensitive, which means that if you capitalize any of the letters, the location won't work:</p>
<pre><code class="lang-shell">curl -I http://nginx-handbook.test/Agatha8

# HTTP/1.1 404 Not Found
# Server: nginx/1.18.0 (Ubuntu)
# Date: Wed, 21 Apr 2021 16:14:29 GMT
# Content-Type: text/html
# Content-Length: 162
# Connection: keep-alive
</code></pre>
<p>To turn this into case insensitive, you'll have to add a <code>*</code> after the <code>~</code> sign.</p>
<pre><code class="lang-conf">events {

}

http {

    server {

        listen 80;
        server_name nginx-handbook.test;

        location ~* /agatha[0-9] {
            return 200 "Miss Marple.\nHercule Poirot.\n";
        }
    }

}
</code></pre>
<p>That will tell NGINX to let go of type sensitivity and match the location anyways.</p>
<pre><code class="lang-shell">curl -I http://nginx-handbook.test/agatha8

# HTTP/1.1 200 OK
# Server: nginx/1.18.0 (Ubuntu)
# Date: Wed, 21 Apr 2021 16:15:04 GMT
# Content-Type: text/plain
# Content-Length: 29
# Connection: keep-alive

curl -I http://nginx-handbook.test/Agatha8

# HTTP/1.1 200 OK
# Server: nginx/1.18.0 (Ubuntu)
# Date: Wed, 21 Apr 2021 16:15:04 GMT
# Content-Type: text/plain
# Content-Length: 29
# Connection: keep-alive
</code></pre>
<p>NGINX assigns priority values to these matches, and a regex match has more priority than a prefix match.</p>
<pre><code class="lang-conf">events {

}

http {

    server {

        listen 80;
        server_name nginx-handbook.test;

        location /Agatha8 {
            return 200 "prefix matched.\n";
        }

        location ~* /agatha[0-9] {
            return 200 "regex matched.\n";
        }
    }

}
</code></pre>
<p>Now if you send a request to http://nginx-handbook.test/Agatha8, you'll get the following response:</p>
<pre><code class="lang-shell">curl -i http://nginx-handbook.test/Agatha8

# HTTP/1.1 200 OK
# Server: nginx/1.18.0 (Ubuntu)
# Date: Thu, 22 Apr 2021 08:08:18 GMT
# Content-Type: text/plain
# Content-Length: 15
# Connection: keep-alive

# regex matched.
</code></pre>
<p>But this priority can be changed a little. The final type of match in NGINX is a <strong>preferential prefix match</strong>. To turn a prefix match into a preferential one, you need to include the <code>^~</code> modifier before the location URI:</p>
<pre><code class="lang-conf">events {

}

http {

    server {

        listen 80;
        server_name nginx-handbook.test;

        location ^~ /Agatha8 {
            return 200 "prefix matched.\n";
        }

        location ~* /agatha[0-9] {
            return 200 "regex matched.\n";
        }
    }

}
</code></pre>
<p>Now if you send a request to http://nginx-handbook.test/Agatha8, you'll get the following response:</p>
<pre><code class="lang-shell">curl -i http://nginx-handbook.test/Agatha8

# HTTP/1.1 200 OK
# Server: nginx/1.18.0 (Ubuntu)
# Date: Thu, 22 Apr 2021 08:13:24 GMT
# Content-Type: text/plain
# Content-Length: 16
# Connection: keep-alive

# prefix matched.
</code></pre>
<p>This time, the prefix match wins. So the list of all the matches in descending order of priority is as follows:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Match</td><td>Modifier</td></tr>
</thead>
<tbody>
<tr>
<td>Exact</td><td><code>=</code></td></tr>
<tr>
<td>Preferential Prefix</td><td><code>^~</code></td></tr>
<tr>
<td>REGEX</td><td><code>~</code> or <code>~*</code></td></tr>
<tr>
<td>Prefix</td><td><code>None</code></td></tr>
</tbody>
</table>
</div><h3 id="heading-variables-in-nginx">Variables in NGINX</h3>
<p>Variables in NGINX are similar to variables in other programming languages. The <code>set</code> directive can be used to declare new variables anywhere within the configuration file:</p>
<pre><code class="lang-conf">set $&lt;variable_name&gt; &lt;variable_value&gt;;

# set name "Farhan"
# set age 25
# set is_working true
</code></pre>
<p>Variables can be of three types</p>
<ul>
<li>String</li>
<li>Integer</li>
<li>Boolean</li>
</ul>
<p>Apart from the variables you declare, there are embedded variables within NGINX modules. An <a target="_blank" href="https://nginx.org/en/docs/varindex.html">alphabetical index of variables</a> is available in the official documentation.</p>
<p>To see some of the variables in action, update the configuration as follows:</p>
<pre><code class="lang-conf">events {

}

http {

    server {

        listen 80;
        server_name nginx-handbook.test;

        return 200 "Host - $host\nURI - $uri\nArgs - $args\n";
    }

}
</code></pre>
<p>Now upon sending a request to the server, you should get a response as follows:</p>
<pre><code class="lang-shell"># curl http://nginx-handbook.test/user?name=Farhan

# Host - nginx-handbook.test
# URI - /user
# Args - name=Farhan
</code></pre>
<p>As you can see, the <code>$host</code> and <code>$uri</code> variables hold the root address and the requested URI relative to the root, respectively. The <code>$args</code> variable, as you can see, contains all the query strings. </p>
<p>Instead of printing the literal string form of the query strings, you can access the individual values using the <code>$arg</code> variable.</p>
<pre><code class="lang-conf">events {

}

http {

    server {

        listen 80;
        server_name nginx-handbook.test;

        set $name $arg_name; # $arg_&lt;query string name&gt;

        return 200 "Name - $name\n";
    }

}
</code></pre>
<p>Now the response from the server should look like as follows:</p>
<pre><code class="lang-shell">curl http://nginx-handbook.test?name=Farhan

# Name - Farhan
</code></pre>
<p>The variables I demonstrated here are embedded in the <a target="_blank" href="https://nginx.org/en/docs/http/ngx_http_core_module.html">ngx_http_core_module</a>. For a variable to be accessible in the configuration, NGINX has to be built with the module embedding the variable. Building NGINX from source and usage of dynamic modules is slightly out of scope for this article. But I'll surely write about that in my blog.</p>
<h3 id="heading-redirects-and-rewrites">Redirects and Rewrites</h3>
<p>A redirect in NGINX is same as redirects in any other platform. To demonstrate how redirects work, update your configuration to look like this:</p>
<pre><code class="lang-conf">events {

}

http {

    include /etc/nginx/mime.types;

    server {

        listen 80;
        server_name nginx-handbook.test;

        root /srv/nginx-handbook-projects/static-demo;

        location = /index_page {
                return 307 /index.html;
        }

        location = /about_page {
                return 307 /about.html;
        }
    }
}
</code></pre>
<p>Now if you send a request to http://nginx-handbook.test/about_page, you'll be redirected to http://nginx-handbook.test/about.html:</p>
<pre><code class="lang-shell">curl -I http://nginx-handbook.test/about_page

# HTTP/1.1 307 Temporary Redirect
# Server: nginx/1.18.0 (Ubuntu)
# Date: Thu, 22 Apr 2021 18:02:04 GMT
# Content-Type: text/html
# Content-Length: 180
# Location: http://nginx-handbook.test/about.html
# Connection: keep-alive
</code></pre>
<p>As you can see, the server responded with a status code of 307 and the location indicates http://nginx-handbook.test/about.html. If you visit http://nginx-handbook.test/about_page from a browser, you'll see that the URL will automatically change to http://nginx-handbook.test/about.html.</p>
<p>A <code>rewrite</code> directive, however, works a little differently. It changes the URI internally, without letting the user know. To see it in action, update your configuration as follows:</p>
<pre><code class="lang-conf">events {

}

http {

    include /etc/nginx/mime.types;

    server {

        listen 80;
        server_name nginx-handbook.test;

        root /srv/nginx-handbook-projects/static-demo;

        rewrite /index_page /index.html;

        rewrite /about_page /about.html;
    }
}
</code></pre>
<p>Now if you send a request to http://nginx-handbook/about_page URI, you'll get a 200 response code and the HTML code for about.html file in response:</p>
<pre><code class="lang-shell">curl -i http://nginx-handbook.test/about_page

# HTTP/1.1 200 OK
# Server: nginx/1.18.0 (Ubuntu)
# Date: Thu, 22 Apr 2021 18:09:31 GMT
# Content-Type: text/html
# Content-Length: 960
# Last-Modified: Wed, 21 Apr 2021 11:27:06 GMT
# Connection: keep-alive
# ETag: "60800c0a-3c0"
# Accept-Ranges: bytes

# &lt;!DOCTYPE html&gt;
# &lt;html lang="en"&gt;
# &lt;head&gt;
#     &lt;meta charset="UTF-8"&gt;
#     &lt;meta http-equiv="X-UA-Compatible" content="IE=edge"&gt;
#     &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
#     &lt;title&gt;NGINX Handbook Static Demo&lt;/title&gt;
#     &lt;link rel="stylesheet" href="mini.min.css"&gt;
#     &lt;style&gt;
#         .container {
#             max-width: 1024px;
#             margin-left: auto;
#             margin-right: auto;
#         }
# 
#         h1 {
#             text-align: center;
#         }
#     &lt;/style&gt;
# &lt;/head&gt;
# &lt;body class="container"&gt;
#     &lt;header&gt;
#         &lt;a class="button" href="index.html"&gt;Index&lt;/a&gt;
#         &lt;a class="button" href="about.html"&gt;About&lt;/a&gt;
#         &lt;a class="button" href="nothing"&gt;Nothing&lt;/a&gt;
#     &lt;/header&gt;
#     &lt;div class="card fluid"&gt;
#         &lt;img src="./the-nginx-handbook.jpg" alt="The NGINX Handbook Cover Image"&gt;
#     &lt;/div&gt;
#     &lt;div class="card fluid"&gt;
#         &lt;h1&gt;this is the &lt;strong&gt;about.html&lt;/strong&gt; file&lt;/h1&gt;
#     &lt;/div&gt;
# &lt;/body&gt;
# &lt;/html&gt;
</code></pre>
<p>And if you visit the URI using a browser, you'll see the about.html page while the URL remains unchanged:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/rewrite.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Apart from the way the URI change is handled, there is another difference between a redirect and rewrite. When a rewrite happens, the <code>server</code> context gets re-evaluated by NGINX. So, a rewrite is a more expensive operation than a redirect.</p>
<h3 id="heading-how-to-try-for-multiple-files">How to Try for Multiple Files</h3>
<p>The final concept I'll be showing in this section is the <code>try_files</code> directive. Instead of responding with a single file, the <code>try_files</code> directive lets you check for the existence of multiple files.</p>
<pre><code class="lang-conf">events {

}

http {

    include /etc/nginx/mime.types;

    server {

        listen 80;
        server_name nginx-handbook.test;

        root /srv/nginx-handbook-projects/static-demo;

        try_files /the-nginx-handbook.jpg /not_found;

        location /not_found {
                return 404 "sadly, you've hit a brick wall buddy!\n";
        }
    }
}
</code></pre>
<p>As you can see, a new <code>try_files</code> directive has been added. By writing <code>try_files /the-nginx-handbook.jpg /not_found;</code> you're instructing NGINX to look for a file named the-nginx-handbook.jpg on the root whenever a request is received. If it doesn't exist, go to the <code>/not_found</code> location. </p>
<p>So now if you visit the server, you'll see the image:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/image-94.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>But if you update the configuration to try for a non-existent file such as blackhole.jpg, you'll get a 404 response with the message "sadly, you've hit a brick wall buddy!".</p>
<p>Now the problem with writing a <code>try_files</code> directive this way is that no matter what URL you visit, as long as a request is received by the server and the the-nginx-handbook.jpg file is found on the disk, NGINX will send that back.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/try-files.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>And that's why <code>try_files</code> is often used with the <code>$uri</code> NGINX variable. </p>
<pre><code class="lang-conf">events {

}

http {

    include /etc/nginx/mime.types;

    server {

        listen 80;
        server_name nginx-handbook.test;

        root /srv/nginx-handbook-projects/static-demo;

        try_files $uri /not_found;

        location /not_found {
                return 404 "sadly, you've hit a brick wall buddy!\n";
        }
    }
}
</code></pre>
<p>By writing <code>try_files $uri /not_found;</code> you're instructing NGINX to try for the URI requested by the client first. If it doesn't find that one, then try the next one.</p>
<p>So now if you visit http://nginx-handbook.test/index.html you should get the old index.html page. The same goes for the about.html page:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/image-95.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>But if you request a file that doesn't exist, you'll get the response from the <code>/not_found</code> location:</p>
<pre><code class="lang-shell">curl -i http://nginx-handbook.test/nothing

# HTTP/1.1 404 Not Found
# Server: nginx/1.18.0 (Ubuntu)
# Date: Thu, 22 Apr 2021 20:01:57 GMT
# Content-Type: text/plain
# Content-Length: 38
# Connection: keep-alive

# sadly, you've hit a brick wall buddy!
</code></pre>
<p>One thing that you may have already noticed is that if you visit the server root http://nginx-handbook.test, you get the 404 response.</p>
<p>This is because when you're hitting the server root, the <code>$uri</code> variable doesn't correspond to any existing file so NGINX serves you the fallback location. If you want to fix this issue, update your configuration as follows:</p>
<pre><code class="lang-conf">events {

}

http {

    include /etc/nginx/mime.types;

    server {

        listen 80;
        server_name nginx-handbook.test;

        root /srv/nginx-handbook-projects/static-demo;

        try_files $uri $uri/ /not_found;

        location /not_found {
                return 404 "sadly, you've hit a brick wall buddy!\n";
        }
    }
}
</code></pre>
<p>By writing <code>try_files $uri $uri/ /not_found;</code> you're instructing NGINX to try for the requested URI first. If that doesn't work then try for the requested URI as a directory, and whenever NGINX ends up into a directory it automatically starts looking for an index.html file.</p>
<p>Now if you visit the server, you should get the index.html file just right:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/image-95.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>The <code>try_files</code> is the kind of directive that can be used in a number of variations. In the upcoming sections, you'll encounter a few other variations but I would suggest that you do some research on the internet regarding the different usage of this directive by yourself.</p>
<h2 id="heading-logging-in-nginx">Logging in NGINX</h2>
<p>By default, NGINX's log files are located inside <code>/var/log/nginx</code>. If you list the content of this directory, you may see something as follows:</p>
<pre><code class="lang-shell">ls -lh /var/log/nginx/

# -rw-r----- 1 www-data adm     0 Apr 25 07:34 access.log
# -rw-r----- 1 www-data adm     0 Apr 25 07:34 error.log
</code></pre>
<p>Let's begin by emptying the two files.</p>
<pre><code class="lang-shell"># delete the old files
sudo rm /var/log/nginx/access.log /var/log/nginx/error.log

# create new files
sudo touch /var/log/nginx/access.log /var/log/nginx/error.log

# reopen the log files
sudo nginx -s reopen
</code></pre>
<p>If you do not dispatch a <code>reopen</code> signal to NGINX, it'll keep writing logs to the previously open streams and the new files will remain empty.</p>
<p>Now to make an entry in the access log, send a request to the server.</p>
<pre><code>curl -I http:<span class="hljs-comment">//nginx-handbook.test</span>

# HTTP/<span class="hljs-number">1.1</span> <span class="hljs-number">200</span> OK
# Server: nginx/<span class="hljs-number">1.18</span><span class="hljs-number">.0</span> (Ubuntu)
# <span class="hljs-built_in">Date</span>: Sun, <span class="hljs-number">25</span> Apr <span class="hljs-number">2021</span> <span class="hljs-number">08</span>:<span class="hljs-number">35</span>:<span class="hljs-number">59</span> GMT
# Content-Type: text/html
# Content-Length: <span class="hljs-number">960</span>
# Last-Modified: Sun, <span class="hljs-number">25</span> Apr <span class="hljs-number">2021</span> <span class="hljs-number">08</span>:<span class="hljs-number">35</span>:<span class="hljs-number">33</span> GMT
# Connection: keep-alive
# ETag: <span class="hljs-string">"608529d5-3c0"</span>
# Accept-Ranges: bytes

sudo cat /<span class="hljs-keyword">var</span>/log/nginx/access.log 

# <span class="hljs-number">192.168</span><span class="hljs-number">.20</span><span class="hljs-number">.20</span> - - [<span class="hljs-number">25</span>/Apr/<span class="hljs-number">2021</span>:<span class="hljs-number">08</span>:<span class="hljs-number">35</span>:<span class="hljs-number">59</span> +<span class="hljs-number">0000</span>] <span class="hljs-string">"HEAD / HTTP/1.1"</span> <span class="hljs-number">200</span> <span class="hljs-number">0</span> <span class="hljs-string">"-"</span> <span class="hljs-string">"curl/7.68.0"</span>
</code></pre><p>As you can see, a new entry has been added to the access.log file. Any request to the server will be logged to this file by default. But we can change this behavior using the <code>access_log</code> directive.</p>
<pre><code class="lang-conf">events {

}

http {

    include /etc/nginx/mime.types;

    server {

        listen 80;
        server_name nginx-handbook.test;

        location / {
            return 200 "this will be logged to the default file.\n";
        }

        location = /admin {
            access_log /var/logs/nginx/admin.log;

            return 200 "this will be logged in a separate file.\n";
        }

        location = /no_logging {
            access_log off;

            return 200 "this will not be logged.\n";
        }
    }
}
</code></pre>
<p>The first <code>access_log</code> directive inside the /admin location block instructs NGINX to write any access log of this URI to the <code>/var/logs/nginx/admin.log</code> file. The second one inside the /no_logging location turns off access logs for this location completely.</p>
<p>Validate and reload the configuration. Now if you send requests to these locations and inspect the log files, you should see something like this:</p>
<pre><code class="lang-shell">curl http://nginx-handbook.test/no_logging
# this will not be logged

sudo cat /var/log/nginx/access.log
# empty

curl http://nginx-handbook.test/admin
# this will be logged in a separate file.

sudo cat /var/log/nginx/access.log
# empty

sudo cat /var/log/nginx/admin.log 
# 192.168.20.20 - - [25/Apr/2021:11:13:53 +0000] "GET /admin HTTP/1.1" 200 40 "-" "curl/7.68.0"

curl  http://nginx-handbook.test/
# this will be logged to the default file.

sudo cat /var/log/nginx/access.log 
# 192.168.20.20 - - [25/Apr/2021:11:15:14 +0000] "GET / HTTP/1.1" 200 41 "-" "curl/7.68.0"
</code></pre>
<p>The error.log file, on the other hand, holds the failure logs. To make an entry to the error.log, you'll have to make NGINX crash. To do so, update your configuration as follows:</p>
<pre><code class="lang-conf">events {

}

http {

    include /etc/nginx/mime.types;

    server {

        listen 80;
        server_name nginx-handbook.test;

        return 200 "..." "...";
    }

}
</code></pre>
<p>As you know, the <code>return</code> directive takes only two parameters – but we've given three here. Now try reloading the configuration and you'll be presented with an error message:</p>
<pre><code class="lang-shell">sudo nginx -s reload

# nginx: [emerg] invalid number of arguments in "return" directive in /etc/nginx/nginx.conf:14
</code></pre>
<p>Check the content of the error log and the message should be present there as well:</p>
<pre><code class="lang-shell">sudo cat /var/log/nginx/error.log 

# 2021/04/25 08:35:45 [notice] 4169#4169: signal process started
# 2021/04/25 10:03:18 [emerg] 8434#8434: invalid number of arguments in "return" directive in /etc/nginx/nginx.conf:14
</code></pre>
<p>Error messages have levels. A <code>notice</code> entry in the error log is harmless, but an <code>emerg</code> or emergency entry has to be addressed right away. </p>
<p>There are eight levels of error messages:</p>
<ul>
<li><code>debug</code> – Useful debugging information to help determine where the problem lies.</li>
<li><code>info</code> – Informational messages that aren't necessary to read but may be good to know.</li>
<li><code>notice</code> – Something normal happened that is worth noting.</li>
<li><code>warn</code> – Something unexpected happened, however is not a cause for concern.</li>
<li><code>error</code> – Something was unsuccessful.</li>
<li><code>crit</code> – There are problems that need to be critically addressed.</li>
<li><code>alert</code> – Prompt action is required.</li>
<li><code>emerg</code> – The system is in an unusable state and requires immediate attention.</li>
</ul>
<p>By default, NGINX records all level of messages. You can override this behavior using the <code>error_log</code> directive. If you want to set the minimum level of a message to be <code>warn</code>, then update your configuration file as follows:</p>
<pre><code class="lang-conf">events {

}

http {

    include /etc/nginx/mime.types;

    server {

        listen 80;
        server_name nginx-handbook.test;

        error_log /var/log/error.log warn;

        return 200 "..." "...";
    }

}
</code></pre>
<p>Validate and reload the configuration, and from now on only messages with a level of <code>warn</code> or above will be logged.</p>
<pre><code class="lang-shell">cat /var/log/nginx/error.log

# 2021/04/25 11:27:02 [emerg] 12769#12769: invalid number of arguments in "return" directive in /etc/nginx/nginx.conf:16
</code></pre>
<p>Unlike the previous output, there are no <code>notice</code> entries here. <code>emerg</code> is a higher level error than <code>warn</code> and that's why it has been logged.</p>
<p>For most projects, leaving the error configuration as it is should be fine. The only suggestion I have is to set the minimum error level to <code>warn</code>. This way you won't have to look at unnecessary entries in the error log. </p>
<p>But if you want to learn more about customizing logging in NGINX, this <a target="_blank" href="https://docs.nginx.com/nginx/admin-guide/monitoring/logging/">link</a> to the official docs may help.</p>
<h2 id="heading-how-to-use-nginx-as-a-reverse-proxy">How to Use NGINX as a Reverse Proxy</h2>
<p>When configured as a reverse proxy, NGINX sits between the client and a back end server. The client sends requests to NGINX, then NGINX passes the request to the back end.</p>
<p>Once the back end server finishes processing the request, it sends it back to NGINX. In turn, NGINX returns the response to the client. </p>
<p>During the whole process, the client doesn't have any idea about who's actually processing the request. It sounds complicated in writing, but once you do it for yourself you'll see how easy NGINX makes it.</p>
<p>Let's see a very basic and impractical example of a reverse proxy:</p>
<pre><code class="lang-conf">events {

}

http {

    include /etc/nginx/mime.types;

    server {
        listen 80;
        server_name nginx.test;

        location / {
                proxy_pass "https://nginx.org/";
        }
    }
}
</code></pre>
<p>Apart from validating and reloading the configuration, you'll also have to add this address to your <code>hosts</code> file to make this demo work on your system:</p>
<pre><code class="lang-hosts">192.168.20.20   nginx.test
</code></pre>
<p>Now if you visit http://nginx.test, you'll be greeted by the original <a target="_blank" href="https://nginx.org">https://nginx.org</a> site while the URI remains unchanged.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/nginx-org-proxy.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>You should be even able to navigate around the site to an extent. If you visit http://nginx.test/en/docs/ you should get the <a target="_blank" href="http://nginx.org/en/docs/">http://nginx.org/en/docs/</a> page in response.</p>
<p>So as you can see, at a basic level, the <code>proxy_pass</code> directive simply passes a client's request to a third party server and reverse proxies the response to the client.</p>
<h3 id="heading-nodejs-with-nginx">Node.js With NGINX</h3>
<p>Now that you know how to configure a basic reverse proxy server, you can serve a Node.js application reverse proxied by NGINX. I've added a demo application inside the repository that comes with this article.</p>
<blockquote>
<p>I'm assuming that you have experience with Node.js and know how to start a Node.js application using PM2.</p>
</blockquote>
<p>If you've already cloned the repository inside <code>/srv/nginx-handbook-projects</code> then the <code>node-js-demo</code> project should be available in the <code>/srv/nginx-handbook-projects/node-js-demo</code> directory.</p>
<p>For this demo to work, you'll need to install Node.js on your server. You can do that following the instructions found <a target="_blank" href="https://github.com/nodesource/distributions#debinstall">here</a>.</p>
<p>The demo application is a simple HTTP server that responds with a 200 status code and a JSON payload. You can start the application by simply executing <code>node app.js</code> but a better way is to use <a target="_blank" href="https://pm2.keymetrics.io">PM2</a>.</p>
<p>For those of you who don't know, PM2 is a daemon process manager widely used in production for Node.js applications. If you want to learn more, this <a target="_blank" href="https://pm2.keymetrics.io/docs/usage/quick-start/">link</a> may help.</p>
<p>Install PM2 globally by executing <code>sudo npm install -g pm2</code>. After the installation is complete, execute following command while being inside the <code>/srv/nginx-handbook-projects/node-js-demo</code> directory:</p>
<pre><code class="lang-shell">pm2 start app.js

# [PM2] Process successfully started
# ┌────┬────────────────────┬──────────┬──────┬───────────┬──────────┬──────────┐
# │ id │ name               │ mode     │ ↺    │ status    │ cpu      │ memory   │
# ├────┼────────────────────┼──────────┼──────┼───────────┼──────────┼──────────┤
# │ 0  │ app                │ fork     │ 0    │ online    │ 0%       │ 21.2mb   │
# └────┴────────────────────┴──────────┴──────┴───────────┴──────────┴──────────┘
</code></pre>
<p>Alternatively you can also do <code>pm2 start /srv/nginx-handbook-projects/node-js-demo/app.js</code> from anywhere on the server. You can stop the application by executing the <code>pm2 stop app</code> command.</p>
<p>The application should be running now but should not be accessible from outside of the server. To verify if the application is running or not, send a get request to http://localhost:3000 from inside your server:</p>
<pre><code class="lang-shell">curl -i localhost:3000

# HTTP/1.1 200 OK
# X-Powered-By: Express
# Content-Type: application/json; charset=utf-8
# Content-Length: 62
# ETag: W/"3e-XRN25R5fWNH2Tc8FhtUcX+RZFFo"
# Date: Sat, 24 Apr 2021 12:09:55 GMT
# Connection: keep-alive
# Keep-Alive: timeout=5

# { "status": "success", "message": "You're reading The NGINX Handbook!" }
</code></pre>
<p>If you get a 200 response, then the server is running fine. Now to configure NGINX as a reverse proxy, open your configuration file and update its content as follows:</p>
<pre><code class="lang-conf">events {

}

http {
    listen 80;
    server_name nginx-handbook.test

    location / {
        proxy_pass http://localhost:3000;
    }
}
</code></pre>
<p>Nothing new to explain here. You're just passing the received request to the Node.js application running at port 3000. Now if you send a request to the server from outside you should get a response as follows:</p>
<pre><code class="lang-shell">curl -i http://nginx-handbook.test

# HTTP/1.1 200 OK
# Server: nginx/1.18.0 (Ubuntu)
# Date: Sat, 24 Apr 2021 14:58:01 GMT
# Content-Type: application/json
# Transfer-Encoding: chunked
# Connection: keep-alive

# { "status": "success", "message": "You're reading The NGINX Handbook!" }
</code></pre>
<p>Although this works for a basic server like this, you may have to add a few more directives to make it work in a real world scenario depending on your application's requirements. </p>
<p>For example, if your application handles web socket connections, then you should update the configuration as follows:</p>
<pre><code class="lang-conf">events {

}

http {
    listen 80;
    server_name nginx-handbook.test

    location / {
        proxy_pass http://localhost:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
    }
}
</code></pre>
<p>The <code>proxy_http_version</code> directive sets the HTTP version for the server. By default it's 1.0, but web socket requires it to be at least 1.1. The <code>proxy_set_header</code> directive is used for setting a header on the back-end server. Generic syntax for this directive is as follows:</p>
<pre><code class="lang-conf">proxy_set_header &lt;header name&gt; &lt;header value&gt;
</code></pre>
<p>So, by writing <code>proxy_set_header Upgrade $http_upgrade;</code> you're instructing NGINX to pass the value of the <code>$http_upgrade</code> variable as a header named <code>Upgrade</code> – same for the <code>Connection</code> header. </p>
<p>If you would like to learn more about web socket proxying, this <a target="_blank" href="https://nginx.org/en/docs/http/websocket.html">link</a> to the official NGINX docs may help.</p>
<p>Depending on the headers required by your application, you may have to set more of them. But the above mentioned configuration is very commonly used to serve Node.js applications.</p>
<h3 id="heading-php-with-nginx">PHP With NGINX</h3>
<p>PHP and NGINX go together like bread and butter. After all the E and the P in the LEMP stack stand for NGINX and PHP.</p>
<blockquote>
<p>I'm assuming you have experience with PHP and know how to run a PHP application.</p>
</blockquote>
<p>I've already included a demo PHP application in the repository that comes with this article. If you've already cloned it in the <code>/srv/nginx-handbook-projects</code> directory, then the application should be inside <code>/srv/nginx-handbook-projects/php-demo</code>.</p>
<p>For this demo to work, you'll have to install a package called PHP-FPM. To install the package, you can execute following command:</p>
<pre><code class="lang-shell">sudo apt install php-fpm -y
</code></pre>
<p>To test out the application, start a PHP server by executing the following command while inside the <code>/srv/nginx-handbook-projects/php-demo</code> directory:</p>
<pre><code class="lang-shell">php -S localhost:8000

# [Sat Apr 24 16:17:36 2021] PHP 7.4.3 Development Server (http://localhost:8000) started
</code></pre>
<p>Alternatively you can also do <code>php -S localhost:8000 /srv/nginx-handbook-projects/php-demo/index.php</code> from anywhere on the server.</p>
<p>The application should be running at port 8000 but it can not be accessed from the outside of the server. To verify, send a get request to http://localhost:8000 from inside your server:</p>
<pre><code class="lang-shell">curl -I localhost:8000

# HTTP/1.1 200 OK
# Host: localhost:8000
# Date: Sat, 24 Apr 2021 16:22:42 GMT
# Connection: close
# X-Powered-By: PHP/7.4.3
# Content-type: application/json

# {"status":"success","message":"You're reading The NGINX Handbook!"}
</code></pre>
<p>If you get a 200 response then the server is running fine. Just like the Node.js configuration, now you can simply <code>proxy_pass</code> the requests to localhost:8000 – but with PHP, there is a better way.</p>
<p>The FPM part in PHP-FPM stands for FastCGI Process Module. FastCGI is a protocol just like HTTP for exchanging binary data. This protocol is slightly faster than HTTP and provides better security. </p>
<p>To use FastCGI instead of HTTP, update your configuration as follows:</p>
<pre><code class="lang-conf">events {

}

http {

      include /etc/nginx/mime.types;

      server {

          listen 80;
          server_name nginx-handbook.test;
          root /srv/nginx-handbook-projects/php-demo;

          index index.php;

          location / {
              try_files $uri $uri/ =404;
          }

          location ~ \.php$ {
              fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
              fastcgi_param REQUEST_METHOD $request_method;
              fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
      }
   }
}
</code></pre>
<p>Let's begin with the new <code>index</code> directive. As you know, NGINX by default looks for an index.html file to serve. But in the demo-project, it's called index.php. So by writing <code>index index.php</code>, you're instructing NGINX to use the index.php file as root instead. </p>
<p>This directive can accept multiple parameters. If you write something like <code>index index.php index.html</code>, NGINX will first look for index.php. If it doesn't find that file, it will look for an index.html file.</p>
<p>The <code>try_files</code> directive inside the first <code>location</code> context is the same as you've seen in a previous section. The <code>=404</code> at the end indicates the error to throw if none of the files are found.</p>
<p>The second <code>location</code> block is the place where the main magic happens. As you can see, we've replaced the <code>proxy_pass</code> directive by a new <code>fastcgi_pass</code>. As the name suggests, it's used to pass a request to a FastCGI service.</p>
<p>The PHP-FPM service by default runs on port 9000 of the host. So instead of using a Unix socket like I've done here, you can pass the request to <code>http://localhost:9000</code> directly. But using a Unix socket is more secure.</p>
<p>If you have multiple PHP-FPM versions installed, you can simply list all the socket file locations by executing the following command:</p>
<pre><code class="lang-shell">sudo find / -name *fpm.sock

# /run/php/php7.4-fpm.sock
# /run/php/php-fpm.sock
# /etc/alternatives/php-fpm.sock
# /var/lib/dpkg/alternatives/php-fpm.sock
</code></pre>
<p>The <code>/run/php/php-fpm.sock</code> file refers to the latest version of PHP-FPM installed on your system. I prefer using the one with the version number. This way even if PHP-FPM gets updated, I'll be certain about the version I'm using.</p>
<p>Unlike passing requests through HTTP, passing requests through FPM requires us to pass some extra information. </p>
<p>The general way of passing extra information to the FPM service is using the <code>fastcgi_param</code> directive. At the very least, you'll have to pass the request method and the script name to the back-end service for the proxying to work. </p>
<p>The <code>fastcgi_param REQUEST_METHOD $request_method;</code> passes the request method to the back-end and the <code>fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;</code> line passes the exact location of the PHP script to run.</p>
<p>At this state, your configuration should work. To test it out, visit your server and you should be greeted by something like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/500-on-fastcgi.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Well, that's weird. A 500 error means NGINX has crashed for some reason. This is where the error logs can come in handy. Let's have a look at the last entry in the error.log file:</p>
<pre><code class="lang-shell">tail -n 1 /var/log/nginx/error.log

# 2021/04/24 17:15:17 [crit] 17691#17691: *21 connect() to unix:/var/run/php/php7.4-fpm.sock failed (13: Permission denied) while connecting to upstream, client: 192.168.20.20, server: nginx-handbook.test, request: "GET / HTTP/1.1", upstream: "fastcgi://unix:/var/run/php/php7.4-fpm.sock:", host: "nginx-handbook.test"
</code></pre>
<p>Seems like the NGINX process is being denied permission to access the PHP-FPM process.</p>
<p>One of the main reasons for getting a permission denied error is user mismatch. Have a look at the user owning the NGINX worker process.</p>
<pre><code class="lang-shell">ps aux | grep nginx

# root         677  0.0  0.4   8892  4260 ?        Ss   14:31   0:00 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
# nobody     17691  0.0  0.3   9328  3452 ?        S    17:09   0:00 nginx: worker process
# vagrant    18224  0.0  0.2   8160  2552 pts/0    S+   17:19   0:00 grep --color=auto nginx
</code></pre>
<p>As you can see, the process is currently owned by <code>nobody</code>. Now inspect the PHP-FPM process.</p>
<pre><code class="lang-shell"># ps aux | grep php

# root       14354  0.0  1.8 195484 18924 ?        Ss   16:11   0:00 php-fpm: master process (/etc/php/7.4/fpm/php-fpm.conf)
# www-data   14355  0.0  0.6 195872  6612 ?        S    16:11   0:00 php-fpm: pool www
# www-data   14356  0.0  0.6 195872  6612 ?        S    16:11   0:00 php-fpm: pool www
# vagrant    18296  0.0  0.0   8160   664 pts/0    S+   17:20   0:00 grep --color=auto php
</code></pre>
<p>This process, on the other hand, is owned by the <code>www-data</code> user. This is why NGINX is being denied access to this process.</p>
<p>To solve this issue, update your configuration as follows:</p>
<pre><code class="lang-conf">user www-data;

events {

}

http {

      include /etc/nginx/mime.types;

      server {

          listen 80;
          server_name nginx-handbook.test;
          root /srv/nginx-handbook-projects/php-demo;

          index index.php;

          location / {
              try_files $uri $uri/ =404;
          }

          location ~ \.php$ {
              fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
              fastcgi_param REQUEST_METHOD $request_method;
              fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
      }
   }
}
</code></pre>
<p>The <code>user</code> directive is responsible for setting the owner for the NGINX worker processes. Now inspect the the NGINX process once again:</p>
<pre><code class="lang-shell"># ps aux | grep nginx

# root         677  0.0  0.4   8892  4264 ?        Ss   14:31   0:00 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
# www-data   20892  0.0  0.3   9292  3504 ?        S    18:10   0:00 nginx: worker process
# vagrant    21294  0.0  0.2   8160  2568 pts/0    S+   18:18   0:00 grep --color=auto nginx
</code></pre>
<p>Undoubtedly the process is now owned by the <code>www-data</code> user. Send a request to your server to check if it's working or not:</p>
<pre><code class="lang-shell"># curl -i http://nginx-handbook.test

# HTTP/1.1 200 OK
# Server: nginx/1.18.0 (Ubuntu)
# Date: Sat, 24 Apr 2021 18:22:24 GMT
# Content-Type: application/json
# Transfer-Encoding: chunked
# Connection: keep-alive

# {"status":"success","message":"You're reading The NGINX Handbook!"}
</code></pre>
<p>If you get a 200 status code with a JSON payload, you're good to go. </p>
<p>This simple configuration is fine for the demo application, but in real-life projects you'll have to pass some additional parameters.</p>
<p>For this reason, NGINX includes a partial configuration called <code>fastcgi_params</code>. This file contains a list of the most common FastCGI parameters.</p>
<pre><code class="lang-shell">cat /etc/nginx/fastcgi_params

# fastcgi_param  QUERY_STRING       $query_string;
# fastcgi_param  REQUEST_METHOD     $request_method;
# fastcgi_param  CONTENT_TYPE       $content_type;
# fastcgi_param  CONTENT_LENGTH     $content_length;

# fastcgi_param  SCRIPT_NAME        $fastcgi_script_name;
# fastcgi_param  REQUEST_URI        $request_uri;
# fastcgi_param  DOCUMENT_URI       $document_uri;
# fastcgi_param  DOCUMENT_ROOT      $document_root;
# fastcgi_param  SERVER_PROTOCOL    $server_protocol;
# fastcgi_param  REQUEST_SCHEME     $scheme;
# fastcgi_param  HTTPS              $https if_not_empty;

# fastcgi_param  GATEWAY_INTERFACE  CGI/1.1;
# fastcgi_param  SERVER_SOFTWARE    nginx/$nginx_version;

# fastcgi_param  REMOTE_ADDR        $remote_addr;
# fastcgi_param  REMOTE_PORT        $remote_port;
# fastcgi_param  SERVER_ADDR        $server_addr;
# fastcgi_param  SERVER_PORT        $server_port;
# fastcgi_param  SERVER_NAME        $server_name;

# PHP only, required if PHP was built with --enable-force-cgi-redirect
# fastcgi_param  REDIRECT_STATUS    200;
</code></pre>
<p>As you can see, this file also contains the <code>REQUEST_METHOD</code> parameter. Instead of passing that manually, you can just include this file in your configuration:</p>
<pre><code class="lang-conf">user www-data;

events {

}

http {

      include /etc/nginx/mime.types;

      server {

          listen 80;
          server_name nginx-handbook.test;
          root /srv/nginx-handbook-projects/php-demo;

          index index.php;

          location / {
              try_files $uri $uri/ =404;
          }

          location ~ \.php$ {
              fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
              fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
              include /etc/nginx/fastcgi_params;
      }
   }
}
</code></pre>
<p>Your server should behave just the same. Apart from the <code>fastcgi_params</code> file, you may also come across the <code>fastcgi.conf</code> file which contains a slightly different set of parameters. I would suggest that you avoid that due to some inconsistencies with its behavior.</p>
<h2 id="heading-how-to-use-nginx-as-a-load-balancer">How to Use NGINX as a Load Balancer</h2>
<p>Thanks to the reverse proxy design of NGINX, you can easily configure it as a load balancer.</p>
<p>I've already added a demo to the repository that comes with this article. If you've already cloned the repository inside the <code>/srv/nginx-handbook-projects/</code> directory then the demo should be in the <code>/srv/nginx-handbook-projects/load-balancer-demo/</code> directory.</p>
<p>In a real life scenario, load balancing may be required on large scale projects distributed across multiple servers. But for this simple demo, I've created three very simple Node.js servers responding with a server number and 200 status code.</p>
<p>For this demo to work, you'll need Node.js installed on the server. You can find instructions in this <a target="_blank" href="https://github.com/nodesource/distributions#debinstall">link</a> to help you get it installed.</p>
<p>Apart from this, you'll also need <a target="_blank" href="https://pm2.keymetrics.io/">PM2</a> for daemonizing the Node.js servers provided in this demo.</p>
<p>If you haven't already, install PM2 by executing <code>sudo npm install -g pm2</code>. After the installation finishes, execute the following commands to start the three Node.js servers:</p>
<pre><code class="lang-shell">pm2 start /srv/nginx-handbook-projects/load-balancer-demo/server-1.js

pm2 start /srv/nginx-handbook-projects/load-balancer-demo/server-2.js

pm2 start /srv/nginx-handbook-projects/load-balancer-demo/server-3.js

pm2 list

# ┌────┬────────────────────┬──────────┬──────┬───────────┬──────────┬──────────┐
# │ id │ name               │ mode     │ ↺    │ status    │ cpu      │ memory   │
# ├────┼────────────────────┼──────────┼──────┼───────────┼──────────┼──────────┤
# │ 0  │ server-1           │ fork     │ 0    │ online    │ 0%       │ 37.4mb   │
# │ 1  │ server-2           │ fork     │ 0    │ online    │ 0%       │ 37.2mb   │
# │ 2  │ server-3           │ fork     │ 0    │ online    │ 0%       │ 37.1mb   │
# └────┴────────────────────┴──────────┴──────┴───────────┴──────────┴──────────┘
</code></pre>
<p>Three Node.js servers should be running on localhost:3001, localhost:3002, localhost:3003 respectively.</p>
<p>Now update your configuration as follows:</p>
<pre><code class="lang-conf">events {

}

http {

    upstream backend_servers {
        server localhost:3001;
        server localhost:3002;
        server localhost:3003;
    }

    server {

        listen 80;
        server_name nginx-handbook.test;

        location / {
            proxy_pass http://backend_servers;
        }
    }
}
</code></pre>
<p>The configuration inside the <code>server</code> context is the same as you've already seen. The <code>upstream</code> context, though, is new. An upstream in NGINX is a collection of servers that can be treated as a single backend.</p>
<p>So the three servers you started using PM2 can be put inside a single upstream and you can let NGINX balance the load between them.</p>
<p>To test out the configuration, you'll have to send a number of requests to the server. You can automate the process using a <code>while</code> loop in bash:</p>
<pre><code class="lang-shell">while sleep 0.5; do curl http://nginx-handbook.test; done

# response from server - 2.
# response from server - 3.
# response from server - 1.
# response from server - 2.
# response from server - 3.
# response from server - 1.
# response from server - 2.
# response from server - 3.
# response from server - 1.
# response from server - 2.
</code></pre>
<p>You can cancel the loop by hitting <code>Ctrl + C</code> on your keyboard. As you can see from the responses from the server, NGINX is load balancing the servers automatically.</p>
<p>Of course, depending on the project scale, load balancing can be a lot more complicated than this. But the goal of this article is to get you started, and I believe you now have a basic understanding of load balancing with NGINX. You can stop the three running server by executing <code>pm2 stop server-1 server-2 server-3</code> command (and it's a good idea here).</p>
<h2 id="heading-how-to-optimize-nginx-for-maximum-performance">How to Optimize NGINX for Maximum Performance</h2>
<p>In this section of the article, you'll learn about a number of ways to get the maximum performance from your server. </p>
<p>Some of these methods will be application-specific, which means they'll probably need tweaking considering your application requirements. But some of them will be general optimization techniques.</p>
<p>Just like the previous sections, changes in configuration will be frequesnt in this one, so don't forget to validate and reload your configuration file every time.</p>
<h3 id="heading-how-to-configure-worker-processes-and-worker-connections">How to Configure Worker Processes and Worker Connections</h3>
<p>As I've already mentioned in a previous section, NGINX can spawn multiple worker processes capable of handling thousands of requests each.</p>
<pre><code class="lang-shell">sudo systemctl status nginx

# ● nginx.service - A high performance web server and a reverse proxy server
#      Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)
#      Active: active (running) since Sun 2021-04-25 08:33:11 UTC; 5h 45min ago
#        Docs: man:nginx(8)
#    Main PID: 3904 (nginx)
#       Tasks: 2 (limit: 1136)
#      Memory: 3.2M
#      CGroup: /system.slice/nginx.service
#              ├─ 3904 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
#              └─16443 nginx: worker process
</code></pre>
<p>As you can see, right now there is only one NGINX worker process on the system. This number, however, can be changed by making a small change to the configuration file.</p>
<pre><code class="lang-conf">worker_processes 2;

events {

}

http {

    server {

        listen 80;
        server_name nginx-handbook.test;

        return 200 "worker processes and worker connections configuration!\n";
    }
}
</code></pre>
<p>The <code>worker_process</code> directive written in the <code>main</code> context is responsible for setting the number of worker processes to spawn. Now check the NGINX service once again and you should see two worker processes:</p>
<pre><code class="lang-shell">sudo systemctl status nginx

# ● nginx.service - A high performance web server and a reverse proxy server
#      Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)
#      Active: active (running) since Sun 2021-04-25 08:33:11 UTC; 5h 54min ago
#        Docs: man:nginx(8)
#     Process: 22610 ExecReload=/usr/sbin/nginx -g daemon on; master_process on; -s reload (code=exited, status=0/SUCCESS)
#    Main PID: 3904 (nginx)
#       Tasks: 3 (limit: 1136)
#      Memory: 3.7M
#      CGroup: /system.slice/nginx.service
#              ├─ 3904 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
#              ├─22611 nginx: worker process
#              └─22612 nginx: worker process
</code></pre>
<p>Setting the number of worker processes is easy, but determining the optimal number of worker processes requires a bit more work.</p>
<p>The worker processes are asynchronous in nature. This means that they will process incoming requests as fast as the hardware can. </p>
<p>Now consider that your server runs on a single core processor. If you set the number of worker processes to 1, that single process will utilize 100% of the CPU capacity. But if you set it to 2, the two processes will be able to utilize 50% of the CPU each. So increasing the number of worker processes doesn't mean better performance.</p>
<p>A rule of thumb in determining the optimal number of worker processes is <strong>number of worker process = number of CPU cores</strong>.</p>
<p>If you're running on a server with a dual core CPU, the number of worker processes should be set to 2. In a quad core it should be set to 4...and you get the idea.</p>
<p>Determining the number of CPUs on your server is very easy on Linux.</p>
<pre><code class="lang-shell">nproc

# 1
</code></pre>
<p>I'm on a single CPU virtual machine, so the <code>nproc</code> detects that there's one CPU. Now that you know the number of CPUs, all that is left to do is set the number on the configuration.</p>
<p>That's all well and good, but every time you upscale the server and the CPU number changes, you'll have to update the server configuration manually.</p>
<p>NGINX provides a better way to deal with this issue. You can simply set the number of worker processes to <code>auto</code> and NGINX will set the number of processes based on the number of CPUs automatically.</p>
<pre><code class="lang-conf">worker_processes auto;

events {

}

http {

    server {

        listen 80;
        server_name nginx-handbook.test;

        return 200 "worker processes and worker connections configuration!\n";
    }
}
</code></pre>
<p>Inspect the NGINX process once again:</p>
<pre><code class="lang-shell">sudo systemctl status nginx

# ● nginx.service - A high performance web server and a reverse proxy server
#      Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)
#      Active: active (running) since Sun 2021-04-25 08:33:11 UTC; 6h ago
#        Docs: man:nginx(8)
#     Process: 22610 ExecReload=/usr/sbin/nginx -g daemon on; master_process on; -s reload (code=exited, status=0/SUCCESS)
#    Main PID: 3904 (nginx)
#       Tasks: 2 (limit: 1136)
#      Memory: 3.2M
#      CGroup: /system.slice/nginx.service
#              ├─ 3904 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
#              └─23659 nginx: worker process
</code></pre>
<p>The number of worker processes is back to one again, because that's what is optimal for this server.</p>
<p>Apart from the worker processes there is also the worker connection, indicating the highest number of connections a single worker process can handle. </p>
<p>Just like the number of worker processes, this number is also related to the number of your CPU core and the number of files your operating system is allowed to open per core.</p>
<p>Finding out this number is very easy on Linux:</p>
<pre><code class="lang-shell">ulimit -n

# 1024
</code></pre>
<p>Now that you have the number, all that is left is to set it in the configuration:</p>
<pre><code class="lang-conf">worker_processes auto;

events {
    worker_connections 1024;
}

http {

    server {

        listen 80;
        server_name nginx-handbook.test;

        return 200 "worker processes and worker connections configuration!\n";
    }
}
</code></pre>
<p>The <code>worker_connections</code> directive is responsible for setting the number of worker connections in a configuration. This is also the first time you're working with the <code>events</code> context. </p>
<p>In a previous section, I mentioned that this context is used for setting values used by NGINX on a general level. The worker connections configuration is one such example.</p>
<h3 id="heading-how-to-cache-static-content">How to Cache Static Content</h3>
<p>The second technique for optimizing your server is caching static content. Regardless of the application you're serving, there is always a certain amount of static content being served, such as stylesheets, images, and so on.</p>
<p>Considering that this content is not likely to change very frequently, it's a good idea to cache them for a certain amount of time. NGINX makes this task easy as well.</p>
<pre><code class="lang-conf">worker_processes auto;

events {
    worker_connections 1024;
}

http {

    include /env/nginx/mime.types;

    server {

        listen 80;
        server_name nginx-handbook.test;

        root /srv/nginx-handbook-demo/static-demo;

        location ~* \.(css|js|jpg)$ {
            access_log off;

            add_header Cache-Control public;
            add_header Pragma public;
            add_header Vary Accept-Encoding;
            expires 1M;
        }
    }
}
</code></pre>
<p>By writing <code>location ~* .(css|js|jpg)$</code> you're instructing NGINX to match requests asking for a file ending with <code>.css</code>, <code>.js</code> and <code>.jpg</code>. </p>
<p>In my applications, I usually store images in the <a target="_blank" href="https://developers.google.com/speed/webp">WebP</a> format even if the user submits a different format. This way, configuring the static cache becomes even easier for me.</p>
<p>You can use the <code>add_header</code> directive to include a header in the response to the client. Previously you've seen the <code>proxy_set_header</code> directive used for setting headers on an ongoing request to the backend server. The <code>add_header</code> directive on the other hand only adds a given header to the response.</p>
<p>By setting the <code>Cache-Control</code> header to public, you're telling the client that this content can be cached in any way. The <code>Pragma</code> header is just an older version of the <code>Cache-Control</code> header and does more or less the same thing. </p>
<p>The next header, <code>Vary</code>, is responsible for letting the client know that this cached content may vary. </p>
<p>The value of <code>Accept-Encoding</code> means that the content may vary depending on the content encoding accepted by the client. This will be clarified further in the next section.</p>
<p>Finally the <code>expires</code> directive allows you to set the <code>Expires</code> header conveniently. The <code>expires</code> directive takes the duration of time this cache will be valid. By setting it to <code>1M</code> you're telling NGINX to cache the content for one month. You can also set this to <code>10m</code> or 10 minutes, <code>24h</code> or 24 hours, and so on.</p>
<p>Now to test out the configuration, sent a request for the the-nginx-handbook.jpg file from the server:</p>
<pre><code class="lang-shell">curl -I http://nginx-handbook.test/the-nginx-handbook.jpg

# HTTP/1.1 200 OK
# Server: nginx/1.18.0 (Ubuntu)
# Date: Sun, 25 Apr 2021 15:58:22 GMT
# Content-Type: image/jpeg
# Content-Length: 19209
# Last-Modified: Sun, 25 Apr 2021 08:35:33 GMT
# Connection: keep-alive
# ETag: "608529d5-4b09"
# Expires: Tue, 25 May 2021 15:58:22 GMT
# Cache-Control: max-age=2592000
# Cache-Control: public
# Pragma: public
# Vary: Accept-Encoding
# Accept-Ranges: bytes
</code></pre>
<p>As you can see, the headers have been added to the response and any modern browser should be able to interpret them.</p>
<h3 id="heading-how-to-compress-responses">How to Compress Responses</h3>
<p>The final optimization technique that I'm going to show today is a pretty straightforward one: compressing responses to reduce their size.</p>
<pre><code class="lang-conf">worker_processes auto;

events {
    worker_connections 1024;
}

http {
    include /env/nginx/mime.types;

    gzip on;
    gzip_comp_level 3;

    gzip_types text/css text/javascript;

    server {

        listen 80;
        server_name nginx-handbook.test;

        root /srv/nginx-handbook-demo/static-demo;

        location ~* \.(css|js|jpg)$ {
            access_log off;

            add_header Cache-Control public;
            add_header Pragma public;
            add_header Vary Accept-Encoding;
            expires 1M;
        }
    }
}
</code></pre>
<p>If you're not already familiar with it, <a target="_blank" href="https://www.gnu.org/software/gzip/">GZIP</a> is a popular file format used by applications for file compression and decompression. NGINX can utilize this format to compress responses using the <code>gzip</code> directives.</p>
<p>By writing <code>gzip on</code> in the <code>http</code> context, you're instructing NGINX to compress responses. The <code>gzip_comp_level</code> directive sets the level of compression. You can set it to a very high number, but that doesn't guarantee better compression. Setting a number between 1 - 4 gives you an efficient result. For example, I like setting it to 3.</p>
<p>By default, NGINX compresses HTML responses. To compress other file formats, you'll have to pass them as parameters to the <code>gzip_types</code> directive. By writing <code>gzip_types text/css text/javascript;</code> you're telling NGINX to compress any file with the mime types of text/css and text/javascript.</p>
<p>Configuring compression in NGINX is not enough. The client has to ask for the compressed response instead of the uncompressed responses. I hope you remember the <code>add_header Vary Accept-Encoding;</code> line in the previous section on caching. This header lets the client know that the response may vary based on what the client accepts.</p>
<p>As an example, if you want to request the uncompressed version of the mini.min.css file from the server, you may do something like this:</p>
<pre><code class="lang-shell">curl -I http://nginx-handbook.test/mini.min.css

# HTTP/1.1 200 OK
# Server: nginx/1.18.0 (Ubuntu)
# Date: Sun, 25 Apr 2021 16:30:32 GMT
# Content-Type: text/css
# Content-Length: 46887
# Last-Modified: Sun, 25 Apr 2021 08:35:33 GMT
# Connection: keep-alive
# ETag: "608529d5-b727"
# Expires: Tue, 25 May 2021 16:30:32 GMT
# Cache-Control: max-age=2592000
# Cache-Control: public
# Pragma: public
# Vary: Accept-Encoding
# Accept-Ranges: bytes
</code></pre>
<p>As you can see, there's nothing about compression. Now if you want to ask for the compressed version of the file, you'll have to send an additional header.</p>
<pre><code class="lang-shell">curl -I -H "Accept-Encoding: gzip" http://nginx-handbook.test/mini.min.css

# HTTP/1.1 200 OK
# Server: nginx/1.18.0 (Ubuntu)
# Date: Sun, 25 Apr 2021 16:31:38 GMT
# Content-Type: text/css
# Last-Modified: Sun, 25 Apr 2021 08:35:33 GMT
# Connection: keep-alive
# ETag: W/"608529d5-b727"
# Expires: Tue, 25 May 2021 16:31:38 GMT
# Cache-Control: max-age=2592000
# Cache-Control: public
# Pragma: public
# Vary: Accept-Encoding
# Content-Encoding: gzip
</code></pre>
<p>As you can see in the response headers, the <code>Content-Encoding</code> is now set to <code>gzip</code> meaning this is the compressed version of the file.</p>
<p>Now if you want to compare the difference in file size, you can do something like this:</p>
<pre><code class="lang-shell">cd ~
mkdir compression-test &amp;&amp; cd compression-test

curl http://nginx-handbook.test/mini.min.css &gt; uncompressed.css

curl -H "Accept-Encoding: gzip" http://nginx-handbook.test/mini.min.css &gt; compressed.css

ls -lh

# -rw-rw-r-- 1 vagrant vagrant 9.1K Apr 25 16:35 compressed.css
# -rw-rw-r-- 1 vagrant vagrant  46K Apr 25 16:35 uncompressed.css
</code></pre>
<p>The uncompressed version of the file is <code>46K</code> and the compressed version is <code>9.1K</code>, almost six times smaller. On real life sites where stylesheets can be much larger, compression can make your responses smaller and faster.</p>
<h2 id="heading-how-to-understand-the-main-configuration-file">How to Understand the Main Configuration File</h2>
<p>I hope you remember the original <code>nginx.conf</code> file you renamed in an earlier section. According to the <a target="_blank" href="https://wiki.debian.org/Nginx/DirectoryStructure">Debian wiki</a>, this file is meant to be changed by the NGINX maintainers and not by server administrators, unless they know exactly what they're doing.</p>
<p>But throughout the entire article, I've taught you to configure your servers in this very file. In this section, however, I'll who you how you should configure your servers without changing the <code>nginx.conf</code> file.</p>
<p>To begin with, first delete or rename your modified <code>nginx.conf</code> file and bring back the original one:</p>
<pre><code class="lang-shell">sudo rm /etc/nginx/nginx.conf

sudo mv /etc/nginx/nginx.conf.backup /etc/nginx/nginx.conf

sudo nginx -s reload
</code></pre>
<p>Now NGINX should go back to its original state. Let's have a look at the content of this file once again by executing the <code>sudo cat /etc/nginx/nginx.conf</code> file:</p>
<pre><code class="lang-conf">user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;

events {
    worker_connections 768;
    # multi_accept on;
}

http {

    ##
    # Basic Settings
    ##

    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;
    # server_tokens off;

    # server_names_hash_bucket_size 64;
    # server_name_in_redirect off;

    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    ##
    # SSL Settings
    ##

    ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE
    ssl_prefer_server_ciphers on;

    ##
    # Logging Settings
    ##

    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;

    ##
    # Gzip Settings
    ##

    gzip on;

    # gzip_vary on;
    # gzip_proxied any;
    # gzip_comp_level 6;
    # gzip_buffers 16 8k;
    # gzip_http_version 1.1;
    # gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

    ##
    # Virtual Host Configs
    ##

    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}


#mail {
#    # See sample authentication script at:
#    # http://wiki.nginx.org/ImapAuthenticateWithApachePhpScript
# 
#    # auth_http localhost/auth.php;
#    # pop3_capabilities "TOP" "USER";
#    # imap_capabilities "IMAP4rev1" "UIDPLUS";
# 
#    server {
#        listen     localhost:110;
#        protocol   pop3;
#        proxy      on;
#    }
# 
#    server {
#        listen     localhost:143;
#        protocol   imap;
#        proxy      on;
#    }
#}
</code></pre>
<p>You should now be able to understand this file without much trouble. On the main context <code>user www-data;</code>, the <code>worker_processes auto;</code> lines should be easily recognizable to you. </p>
<p>The line <code>pid /run/nginx.pid;</code> sets the process ID for the NGINX process and <code>include /etc/nginx/modules-enabled/*.conf;</code> includes any configuration file found on the <code>/etc/nginx/modules-enabled/</code> directory. </p>
<p>This directory is meant for NGINX dynamic modules. I haven't covered dynamic modules in this article so I'll skip that.</p>
<p>Now inside the the <code>http</code> context, under basic settings you can see some common optimization techniques applied. Here's what these techniques do:</p>
<ul>
<li><code>sendfile on;</code> disables buffering for static files.</li>
<li><code>tcp_nopush on;</code> allows sending response header in one packet.</li>
<li><code>tcp_nodelay on;</code> disables <a target="_blank" href="https://en.wikipedia.org/wiki/Nagle's_algorithm">Nagle's Algorithm</a> resulting in faster static file delivery.</li>
</ul>
<p>The <code>keepalive_timeout</code> directive indicates how long to keep a connection open and the <code>types_hash_maxsize</code> directive sets the size of the types hash map. It also includes the <code>mime.types</code> file by default.</p>
<p>I'll skip the SSL settings simply because we haven't covered them in this article. We've already discussed the logging and gzip settings. You may see some of the directives regarding gzip as commented. As long as you understand what you're doing, you may customize these settings.</p>
<p>You use the <code>mail</code> context to configure NGINX as a mail server. We've only talked about NGINX as a web server so far, so I'll skip this as well.</p>
<p>Now under the virtual hosts settings, you should see two lines as follows:</p>
<pre><code class="lang-conf">##
# Virtual Host Configs
##

include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
</code></pre>
<p>These two lines instruct NGINX to include any configuration files found inside the <code>/etc/nginx/conf.d/</code> and <code>/etc/nginx/sites-enabled/</code> directories.</p>
<p>After seeing these two lines, people often take these two directories as the ideal place to put their configuration files, but that's not right.</p>
<p>There is another directory <code>/etc/nginx/sites-available/</code> that's meant to store configuration files for your virtual hosts. The <code>/etc/nginx/sites-enabled/</code> directory is meant for storing the symbolic links to the files from the <code>/etc/nginx/sites-available/</code> directory. </p>
<p>In fact there is an example configuration:</p>
<pre><code class="lang-shell">ln -lh /etc/nginx/sites-enabled/

# lrwxrwxrwx 1 root root 34 Apr 25 08:33 default -&gt; /etc/nginx/sites-available/default
</code></pre>
<p>As you can see, the directory contains a symbolic link to the <code>/etc/nginx/sites-available/default</code> file.</p>
<p>The idea is to write multiple virtual hosts inside the <code>/etc/nginx/sites-available/</code> directory and make some of them active by symbolic linking them to the <code>/etc/nginx/sites-enabled/</code> directory.</p>
<p>To demonstrate this concept, let's configure a simple static server. First, delete the default virtual host symbolic link, deactivating this configuration in the process:</p>
<pre><code class="lang-shell">sudo rm /etc/nginx/sites-enabled/default

ls -lh /etc/nginx/sites-enabled/

# lrwxrwxrwx 1 root root 41 Apr 25 18:01 nginx-handbook -&gt; /etc/nginx/sites-available/nginx-handbook
</code></pre>
<p>Create a new file by executing <code>sudo touch /etc/nginx/sites-available/nginx-handbook</code> and put the following content in there:</p>
<pre><code>server {
    listen <span class="hljs-number">80</span>;
    server_name nginx-handbook.test;

    root /srv/nginx-handbook-projects/<span class="hljs-keyword">static</span>-demo;
}
</code></pre><p>Files inside the <code>/etc/nginx/sites-available/</code> directory are meant to be included within the main <code>http</code> context so they should contain <code>server</code> blocks only.</p>
<p>Now create a symbolic link to this file inside the <code>/etc/nginx/sites-enabled/</code> directory by executing the following command:</p>
<pre><code class="lang-shell">sudo ln -s /etc/nginx/sites-available/nginx-handbook /etc/nginx/sites-enabled/nginx-handbook

ls -lh /etc/nginx/sites-enabled/

# lrwxrwxrwx 1 root root 34 Apr 25 08:33 default -&gt; /etc/nginx/sites-available/default
# lrwxrwxrwx 1 root root 41 Apr 25 18:01 nginx-handbook -&gt; /etc/nginx/sites-available/nginx-handbook
</code></pre>
<p>Before validating and reloading the configuration file, you'll have to reopen the log files. Otherwise you may get a permission denied error. This happens because the process ID is different this time as a result of swapping the old <code>nginx.conf</code> file.</p>
<pre><code class="lang-shell">sudo rm /var/log/nginx/*.log

sudo touch /var/log/nginx/access.log /var/log/nginx/error.log

sudo nginx -s reopen
</code></pre>
<p>Finally, validate and reload the configuration file:</p>
<pre><code>sudo nginx -t

# nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
# nginx: configuration file /etc/nginx/nginx.conf test is successful

sudo nginx -s reload
</code></pre><p>Visit the server and you should be greeted with the good old The NGINX Handbook page:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/image-100.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>If you've configured the server correctly and you're still getting the old NGINX welcome page, perform a hard refresh. The browser often holds on to old assets and requires a little cleanup.</p>
<h2 id="heading-how-to-configure-ssl-and-http2">How To Configure SSL and HTTP/2</h2>
<p><a target="_blank" href="https://en.wikipedia.org/wiki/HTTP/2">HTTP/2</a> is the newest version of the wildly popular Hyper Text Transport Protocol. Based on Google's experimental <a target="_blank" href="https://en.wikipedia.org/wiki/SPDY">SPDY</a> protocol, HTTP/2 provides better performance by introducing features like full request and response multiplexing, better compression of header fields, server push and request prioritization.</p>
<p>Some of the notable features of HTTP/2 is as follows:</p>
<ol>
<li><strong>Binary Protocol</strong> - While HTTP/1.x was a text based protocol, HTTP/2 is a binary protocol resulting in less error during data transfer process.</li>
<li><strong>Multiplexed Streams</strong> - All HTTP/2 connections are multiplexed streams meaning multiple files can be transferred in a single stream of binary data.</li>
<li><strong>Compressed Header</strong> - HTTP/2 compresses header data in responses resulting in faster transfer of data.</li>
<li><strong>Server Push</strong> - This capability allows the server to send linked resources to the client automatically, greatly reducing the number of requests to the server.</li>
<li><strong>Stream Prioritization</strong> - HTTP/2 can prioritize data streams based on their type resulting in better bandwidth allocation where necessary.</li>
</ol>
<blockquote>
<p>If you want to learn more about the improvements in HTTP/2 this <a target="_blank" href="https://kinsta.com/learn/what-is-http2/">article</a> by <a target="_blank" href="https://kinsta.com/">Kinsta</a> may help.</p>
</blockquote>
<p>While a significant upgrade over its predecessor, HTTP/2 is not as widely adapted as it should have been. In this section, I'll introduce you to some of the new features mentioned previously and I'll also show you how to enable HTTP/2 on your NGINX powered web server.</p>
<p>For this section, I'll be using the <code>static-demo</code> project. I'm assuming you've already cloned the repository inside <code>/srv/nginx-handbook-projects</code> directory. If you haven't, this is the time to do so. Also, this section has to be done on a virtual private server instead of a virtual machine.</p>
<p>For simplicity, I'll use the <code>/etc/nginx/sites-available/default</code> file as my configuration. Open the file using <code>nano</code> or <code>vi</code> if you fancy that.</p>
<pre><code class="lang-shell">nano /etc/nginx/sites-available/default
</code></pre>
<p>Update the file's content as follows:</p>
<pre><code class="lang-conf">server {
        listen 80;
        server_name nginx-handbook.farhan.dev;  

        root /srv/nginx-handbook-projects/static-demo;
}
</code></pre>
<p>As you can see, the <code>/srv/nginx-handbook-projects/static-demo;</code> directory has been set as the root of this site and <code>nginx-handbook.farhan.dev</code> has been set as the server name. If you do not have a custom domain set up, you can use your server's IP address as the server name here.</p>
<pre><code class="lang-shell">nginx -t

# nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
# nginx: configuration file /etc/nginx/nginx.conf test is successful

nginx -s reload
</code></pre>
<p>Test the configuration by executing <code>nginx -t</code> and reload the configuration by executing <code>nginx -s reload</code> commands.</p>
<p>Finally visit your server and you should be greeted with a simple static HTML page.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/05/OCHoJEYdm.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>One of the pre-requisite to have HTTP/2 working on your server is to have a valid SSL certificate. Lets do that first.</p>
<h3 id="heading-how-to-configure-ssl">How To Configure SSL</h3>
<p>For those of you who may not know, an SSL certificate is what allows a server to make the move from HTTP to HTTPS. These certificates are issued by a certificate authority (CA). Most of the authorities charge a fee for issuing certificates but nonprofit authorities such as <a target="_blank" href="https://letsencrypt.org/">Let's Encrypt</a>, issues certificates for free.</p>
<blockquote>
<p>If you want to understand the theory of SSL in a bit more detail, this <a target="_blank" href="https://www.cloudflare.com/learning/ssl/what-is-an-ssl-certificate/">article</a> on the <a target="_blank" href="https://www.cloudflare.com/learning/">Cloudflare Learning Center</a> may help.</p>
</blockquote>
<p>Thanks to open-source tools like <a target="_blank" href="https://github.com/certbot/certbot">Certbot</a>, installing a free certificate is dead easy. Head over to <a target="_blank" href="https://certbot.eff.org/">certbot.eff.org</a> link. Now select the software and system that powers your server.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/05/A13UVsSsE.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>I'm running NGINX on Ubuntu 20.04 and if you've been in line with this article, you should have the same combination.</p>
<p>After selecting your combination of software and system, you'll be forwarded to a new page containing step by step instructions for installing certbot and a new SSL certificate.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/05/27NSXHEp2.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>The installation steps for certbot may differ from system to system but rest of the instructions should remain same. On Ubuntu, the recommended way is to use <a target="_blank" href="https://snapcraft.io/">snap</a>.</p>
<pre><code class="lang-shell">snap install --classic certbot

# certbot 1.14.0 from Certbot Project (certbot-eff✓) installed

certbot --version

# certbot 1.14.0
</code></pre>
<p>Certbot is now installed and ready to be used. Before you install a new certificate, make sure the NGINX configuration file contains all the necessary server names. Such as, if you want to install a new certificate for <code>yourdomain.tld</code> and <code>www.yourdomain.tld</code>, you'll have to include both of them in your configuration.</p>
<p>Once you're happy with your configuration, you can install a newly provisioned certificate for your server. To do so, execute the <code>certbot</code> program with <code>--nginx</code> option.</p>
<pre><code class="lang-shell">certbot --nginx

# Saving debug log to /var/log/letsencrypt/letsencrypt.log
# Plugins selected: Authenticator nginx, Installer nginx
# Enter email address (used for urgent renewal and security notices)
#  (Enter 'c' to cancel): shovik.is.here@gmail.com

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Please read the Terms of Service at
# https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf. You must
# agree in order to register with the ACME server. Do you agree?
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# (Y)es/(N)o: Y

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Would you be willing, once your first certificate is successfully issued, to
# share your email address with the Electronic Frontier Foundation, a founding
# partner of the Let's Encrypt project and the non-profit organization that
# develops Certbot? We'd like to send you email about our work encrypting the web,
# EFF news, campaigns, and ways to support digital freedom.
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# (Y)es/(N)o: N
# Account registered.

# Which names would you like to activate HTTPS for?
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# 1: nginx-handbook.farhan.dev
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Select the appropriate numbers separated by commas and/or spaces, or leave input
# blank to select all options shown (Enter 'c' to cancel): 
# Requesting a certificate for nginx-handbook.farhan.dev
# Performing the following challenges:
# http-01 challenge for nginx-handbook.farhan.dev
# Waiting for verification...
# Cleaning up challenges
# Deploying Certificate to VirtualHost /etc/nginx/sites-enabled/default
# Redirecting all traffic on port 80 to ssl in /etc/nginx/sites-enabled/default

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Congratulations! You have successfully enabled
# https://nginx-handbook.farhan.dev
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

# IMPORTANT NOTES:
#  - Congratulations! Your certificate and chain have been saved at:
#    /etc/letsencrypt/live/nginx-handbook.farhan.dev/fullchain.pem
#    Your key file has been saved at:
#    /etc/letsencrypt/live/nginx-handbook.farhan.dev/privkey.pem
#    Your certificate will expire on 2021-07-30. To obtain a new or
#    tweaked version of this certificate in the future, simply run
#    certbot again with the "certonly" option. To non-interactively
#    renew *all* of your certificates, run "certbot renew"
#  - If you like Certbot, please consider supporting our work by:

#    Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
#    Donating to EFF:                    https://eff.org/donate-le
</code></pre>
<p>You'll be asked for an emergency contact email address, license agreement and if you would like to receive emails from them or not.</p>
<p>The certbot program will automatically read the server names from your configuration file and show you a list of them. If you have multiple virtual hosts on your server, certbot will recognize them as well.</p>
<p>Finally if the installation is successful, you'll be congratulated by the program. To verify if everything's working or not, visit your server with HTTPS this time:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/05/mCbapBf9n.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>As you can see, HTTPS has been enabled successfully and you can confirm that the certificate is verified by <a target="_blank" href="https://letsencrypt.org/">Let's Encrypt</a> authority. Later on, if you add new virtual hosts to this server with new domains or sub domains, you'll have to reinstall the certificates.</p>
<p>It's also possible to install wildcard certificate such as <code>*.yourdomain.tld</code> for some supported DNS managers. Detailed instructions can be found on the previously shown installation instruction page.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/05/PLFcZoO8P.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>A newly installed certificate will be valid for 90 days. After that, a renewal will be required. Certbot does the renewal automatically. You can execute <code>certbot renew</code> command with the <code>--dry-run</code> option to test out the auto renewal feature.</p>
<pre><code class="lang-shell">certbot renew --dry-run

# Saving debug log to /var/log/letsencrypt/letsencrypt.log

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Processing /etc/letsencrypt/renewal/nginx-handbook.farhan.dev.conf
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Cert not due for renewal, but simulating renewal for dry run
# Plugins selected: Authenticator nginx, Installer nginx
# Account registered.
# Simulating renewal of an existing certificate for nginx-handbook.farhan.dev
# Performing the following challenges:
# http-01 challenge for nginx-handbook.farhan.dev
# Waiting for verification...
# Cleaning up challenges

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# new certificate deployed with reload of nginx server; fullchain is
# /etc/letsencrypt/live/nginx-handbook.farhan.dev/fullchain.pem
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Congratulations, all simulated renewals succeeded: 
#   /etc/letsencrypt/live/nginx-handbook.farhan.dev/fullchain.pem (success)
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
</code></pre>
<p>The command will simulate a certificate renewal to test if it's correctly set up or not. If it succeeds you'll be congratulated by the program. This step ends the procedure of installing an SSL certificate on your server.</p>
<p>To understand what certbot did behind the scenes, open up the <code>/etc/nginx/sites-available/default</code> file once again and see how its content has been altered.</p>
<pre><code class="lang-conf">server {

    server_name nginx-handbook.farhan.dev;  

    root /srv/nginx-handbook-projects/static-demo;

    listen [::]:443 ssl ipv6only=on; # managed by Certbot
    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/nginx-handbook.farhan.dev/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/nginx-handbook.farhan.dev/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

}
server {
    if ($host = nginx-handbook.farhan.dev) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


    listen 80;
    listen [::]:80;

    server_name nginx-handbook.farhan.dev;
    return 404; # managed by Certbot
}
</code></pre>
<p>As you can see, certbot has added quite a few lines here. I'll explain the notable ones.</p>
<pre><code class="lang-conf">server {
    # ...
    listen [::]:443 ssl ipv6only=on; # managed by Certbot
    listen 443 ssl; # managed by Certbot
    # ...
}
</code></pre>
<p>Like the 80 port, 443 is widely used for listening to HTTPS requests. By writing <code>listen 443 ssl;</code> certbot is instructing NGINX to listen for any HTTPS request on port 443. The <code>listen [::]:443 ssl ipv6only=on;</code> line is for handling IPV6 connections.</p>
<pre><code class="lang-conf">

server {
    # ...
    ssl_certificate /etc/letsencrypt/live/nginx-handbook.farhan.dev/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/nginx-handbook.farhan.dev/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
    # ...
}
</code></pre>
<p>The <code>ssl_certificate</code> directive is used for indicating the location of the certificate and the private key file on your server. The <code>/etc/letsencrypt/options-ssl-nginx.conf;</code> includes some common directives necessary for SSL.</p>
<p>Finally the <code>ssl_dhparam</code> indicates to the file defining how OpenSSL is going to perform <a target="_blank" href="https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange">Diffie–Hellman key exchange</a>. If you want to learn more about the purpose of <code>/etc/letsencrypt/ssl-dhparams.pem;</code> file, this stack exchange <a target="_blank" href="https://security.stackexchange.com/questions/94390/whats-the-purpose-of-dh-parameters">thread</a> may help you.</p>
<pre><code class="lang-conf">server {
    if ($host = nginx-handbook.farhan.dev) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


    listen 80;
    listen [::]:80;

    server_name nginx-handbook.farhan.dev;
    return 404; # managed by Certbot
}
</code></pre>
<p>This newly added server block is responsible for redirecting any HTTP requests to HTTPS disabling HTTP access completely.</p>
<h3 id="heading-how-to-enable-http2">How To Enable HTTP/2</h3>
<p>Once you've successfully installed a valid SSL certificate on your server, you're ready to enable HTTP/2. SSL is a prerequisite for HTTP/2, so right off the bat you can see, security is not optional in HTTP/2.</p>
<p>HTTP/2 support for NGINX is provided by the <a target="_blank" href="https://nginx.org/en/docs/http/ngx_http_v2_module.html">ngx_http_v2_module</a> module. Pre-built binaries of NGINX on most of the systems come with this module baked in. If you've built NGINX from source however, you'll have to include this module manually.</p>
<p>Before upgrading to HTTP/2, send a request to your server and see the current protocol version.</p>
<pre><code class="lang-shell">curl -I -L https://nginx-handbook.farhan.dev

# HTTP/1.1 200 OK
# Server: nginx/1.18.0 (Ubuntu)
# Date: Sat, 01 May 2021 10:46:36 GMT
# Content-Type: text/html
# Content-Length: 960
# Last-Modified: Fri, 30 Apr 2021 20:14:48 GMT
# Connection: keep-alive
# ETag: "608c6538-3c0"
# Accept-Ranges: bytes
</code></pre>
<p>As you can see, by default the server is on HTTP/1.1 protocol. On the next step, we'll update the configuration file as necessary for enabling HTTP/2.</p>
<p>To enable HTTP/2 on your server, open the <code>/etc/nginx/sites-available/default</code> file once again. Find wherever it says <code>listen [::]:443 ssl ipv6only=on;</code> or <code>listen 443 ssl;</code> and update them to <code>listen [::]:443 ssl http2 ipv6only=on;</code> and <code>listen 443 ssl http2;</code> respectively.</p>
<pre><code class="lang-conf">server {

    server_name nginx-handbook.farhan.dev;  

    root /srv/nginx-handbook-projects/static-demo;

    listen [::]:443 ssl http2 ipv6only=on; # managed by Certbot
    listen 443 ssl http2; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/nginx-handbook.farhan.dev/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/nginx-handbook.farhan.dev/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

}
server {
    if ($host = nginx-handbook.farhan.dev) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


    listen 80;
    listen [::]:80;

    server_name nginx-handbook.farhan.dev;
    return 404; # managed by Certbot
}
</code></pre>
<p>Test the configuration file by executing <code>niginx -t</code> and reload the configuration by executing <code>nginx -s reload</code> commands. Now send a request to your server again.</p>
<pre><code class="lang-shell">curl -I -L https://nginx-handbook.farhan.dev

# HTTP/2 200 
# server: nginx/1.18.0 (Ubuntu)
# date: Sat, 01 May 2021 09:03:10 GMT
# content-type: text/html
# content-length: 960
# last-modified: Fri, 30 Apr 2021 20:14:48 GMT
# etag: "608c6538-3c0"
# accept-ranges: bytes
</code></pre>
<p>As you can see, HTTP/2 has been enabled for any client supporting the new protocol.</p>
<h3 id="heading-how-to-enable-server-push">How to Enable Server Push</h3>
<p>Server push is one of the many features that HTTP/2 brings to the table. Which means the server can push files to the client without the client having to request for them. In a HTTP/1.x server, a typical request for static content may look like as follows:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/05/f49aVyg9h.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>But on a server push enabled HTTP/2 server, it may look like as follows:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/05/wLod0KcsB.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>On a single request for the index.html file the server responds with the style.css file as well, minimizing the number of requests in the process.</p>
<p>In this section, I'll use an open-source HTTP client named <a target="_blank" href="https://nghttp2.org/">Nghttp2</a> for testing the server.</p>
<pre><code class="lang-shell">apt install nghttp2-client -y

# Reading package lists... Done
# Building dependency tree       
# Reading state information... Done
# The following additional packages will be installed:
#   libev4 libjansson4 libjemalloc2
# The following NEW packages will be installed:
#   libev4 libjansson4 libjemalloc2 nghttp2-client
# 0 upgraded, 4 newly installed, 0 to remove and 0 not upgraded.
# Need to get 447 kB of archives.
# After this operation, 1,520 kB of additional disk space will be used.
# Get:1 http://archive.ubuntu.com/ubuntu focal/main amd64 libjansson4 amd64 2.12-1build1 [28.9 kB]
# Get:2 http://archive.ubuntu.com/ubuntu focal/universe amd64 libjemalloc2 amd64 5.2.1-1ubuntu1 [235 kB]
# Get:3 http://archive.ubuntu.com/ubuntu focal/universe amd64 libev4 amd64 1:4.31-1 [31.2 kB]
# Get:4 http://archive.ubuntu.com/ubuntu focal/universe amd64 nghttp2-client amd64 1.40.0-1build1 [152 kB]
# Fetched 447 kB in 1s (359 kB/s)     
# Selecting previously unselected package libjansson4:amd64.
# (Reading database ... 107613 files and directories currently installed.)
# Preparing to unpack .../libjansson4_2.12-1build1_amd64.deb ...
# Unpacking libjansson4:amd64 (2.12-1build1) ...
# Selecting previously unselected package libjemalloc2:amd64.
# Preparing to unpack .../libjemalloc2_5.2.1-1ubuntu1_amd64.deb ...
# Unpacking libjemalloc2:amd64 (5.2.1-1ubuntu1) ...
# Selecting previously unselected package libev4:amd64.
# Preparing to unpack .../libev4_1%3a4.31-1_amd64.deb ...
# Unpacking libev4:amd64 (1:4.31-1) ...
# Selecting previously unselected package nghttp2-client.
# Preparing to unpack .../nghttp2-client_1.40.0-1build1_amd64.deb ...
# Unpacking nghttp2-client (1.40.0-1build1) ...
# Setting up libev4:amd64 (1:4.31-1) ...
# Setting up libjemalloc2:amd64 (5.2.1-1ubuntu1) ...
# Setting up libjansson4:amd64 (2.12-1build1) ...
# Setting up nghttp2-client (1.40.0-1build1) ...
# Processing triggers for man-db (2.9.1-1) ...
# Processing triggers for libc-bin (2.31-0ubuntu9.2) ...

nghttp --version

# nghttp nghttp2/1.40.0
</code></pre>
<p>Lets test by sending a request to the server without server push.</p>
<pre><code class="lang-shell">nghttp --null-out --stat https://nginx-handbook.farhan.dev/index.html

# id  responseEnd requestStart  process code size request path
#  13      +836us       +194us    642us  200  492 /index.html

nghttp --null-out --stat --get-assets https://nginx-handbook.farhan.dev/index.html

# id  responseEnd requestStart  process code size request path
#  13      +836us       +194us    642us  200  492 /index.html
#  15     +3.11ms      +2.65ms    457us  200  45K /mini.min.css
#  17     +3.23ms      +2.65ms    578us  200  18K /the-nginx-handbook.jpg
</code></pre>
<p>On the first request <code>--null-out</code> means discard downloaded data and <code>--stat</code> means print statistics on terminal. On the second request <code>--get-assets</code> means also download assets such as stylesheets, images and scripts linked to this files. As a result you can tell by the <code>requestStart</code> times, the css file and image was downloaded shortly after the html file was downloaded.</p>
<p>Now, lets enable server push for stylesheets and images. Open <code>/etc/nginx/sites-available/default</code> file and update its content as follows:</p>
<pre><code class="lang-conf">server {

    server_name nginx-handbook.farhan.dev;

    root /srv/nginx-handbook-projects/static-demo;

    listen [::]:443 ssl http2 ipv6only=on; # managed by Certbot
    listen 443 ssl http2; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/nginx-handbook.farhan.dev/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/nginx-handbook.farhan.dev/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

    location = /index.html {
        http2_push /mini.min.css;
        http2_push /the-nginx-handbook.jpg;
    }

    location = /about.html {
        http2_push /mini.min.css;
        http2_push /the-nginx-handbook.jpg;
    }

}
server {
    if ($host = nginx-handbook.farhan.dev) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


    listen 80;
    listen [::]:80;

    server_name nginx-handbook.farhan.dev;
    return 404; # managed by Certbot
}
</code></pre>
<p>Two location blocks have been added to exactly match <code>/index.html</code> and <code>/about.html</code> locations. The <code>http2_push</code> directive is used for sending back additional response. Now whenever NGINX receives a request for one of these two locations, it'll automatically send back the css and image file.</p>
<p>Test the configuration by executing <code>nginx -t</code> and reload the configuration by executing <code>nginx -s reload</code> commands.</p>
<pre><code class="lang-shell">nginx -t

# nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
# nginx: configuration file /etc/nginx/nginx.conf test is successful

nginx -s reload
</code></pre>
<p>Now send another request to the server using <code>nghttp</code> and do not include <code>--get-assets</code> option.</p>
<pre><code class="lang-shell">nghttp --null-out --stat https://nginx-handbook.farhan.dev/index.html

# id  responseEnd requestStart  process code size request path
#  13     +1.49ms       +254us   1.23ms  200  492 /index.html
#   2     +1.56ms *    +1.35ms    212us  200  45K /mini.min.css
#   4     +1.71ms *    +1.39ms    318us  200  18K /the-nginx-handbook.jpg
</code></pre>
<p>As you can see, although the assets were not requested, the server has sent them to the client. Looking at the time measurements, process time has gone down and the three responses ended almost simultaneously.</p>
<p>This was a very simple example of server push but depending on the necessities of your project, this configuration can become much complex. This <a target="_blank" href="https://www.nginx.com/blog/nginx-1-13-9-http2-server-push/">article</a> by <a target="_blank" href="https://www.nginx.com/people/owen-garrett/">Owen Garrett</a> on the official NGINX blog can help you with more complex server push configuration.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>I would like to thank you from the bottom of my heart for the time you've spent on reading this article. I hope you've enjoyed your time and have learned all the essentials of NGINX.</p>
<p>Apart from this one, I've written full-length handbooks on other complicated topics available for free on <a target="_blank" href="https://www.freecodecamp.org/news/author/farhanhasin/">freeCodeCamp</a>.</p>
<p>These handbooks are part of my mission to simplify hard to understand technologies for everyone. Each of these handbooks takes a lot of time and effort to write.</p>
<p>If you've enjoyed my writing and want to keep me motivated, consider leaving starts on <a target="_blank" href="https://github.com/fhsinchy/">GitHub</a> and endorse me for relevant skills on <a target="_blank" href="https://www.linkedin.com/in/farhanhasin/">LinkedIn</a>. I also accept sponsorship so you may consider <a target="_blank" href="https://www.buymeacoffee.com/farhanhasin">buying me a coffee</a> if you want to.</p>
<p>I'm always open to suggestions and discussions on <a target="_blank" href="https://twitter.com/frhnhsin">Twitter</a> or <a target="_blank" href="https://www.linkedin.com/in/farhanhasin/">LinkedIn</a>. Hit me with direct messages.</p>
<p>In the end, consider sharing the resources with others, because </p>
<blockquote>
<p>Sharing knowledge is the most fundamental act of friendship. Because it is a way you can give something without loosing something. — Richard Stallman</p>
</blockquote>
<p>Till the next one, stay safe and keep learning.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Deploy a React App to Production Using Docker and NGINX with API Proxies ]]>
                </title>
                <description>
                    <![CDATA[ This post will help you to learn how to deploy your React applications to production. We are going to use Docker and NGINX to secure API keys and proxy requests to prevent Cross-Origin Resource Sharing (CORS) violations. You can find the code and vid... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-deploy-react-apps-to-production/</link>
                <guid isPermaLink="false">66bc4d08d94fa6cb67b8449a</guid>
                
                    <category>
                        <![CDATA[ deployment ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Docker ]]>
                    </category>
                
                    <category>
                        <![CDATA[ nginx ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Matéu.sh ]]>
                </dc:creator>
                <pubDate>Wed, 17 Mar 2021 16:43:55 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2021/03/fringer-cat-hddmXlPaFGo-unsplash.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>This post will help you to learn how to deploy your React applications to production. We are going to use Docker and NGINX to secure API keys and proxy requests to prevent Cross-Origin Resource Sharing (CORS) violations.</p>
<p>You can find the <strong>code</strong> and <strong>video</strong> in the summary at the end.</p>
<h3 id="heading-what-youll-learn-in-this-article">What You'll Learn in This Article</h3>
<p>In every project lifecycle, the time comes to publish it, and it is not always that obvious how to do so. The production environment is different than the development one, and users will not take any extra steps to run it. Most web apps consume some sort of APIs, and often, they are hosted on a different server. </p>
<p>In this case, as a developer, we need to solve Cross-Origin Resource Sharing (CORS) issues. Too often, we end up building a backend even though it is not necessary. I believe that developers should keep their applications simple, and cut out all redundant pieces. </p>
<p>In this article, I would like to show you how I prepare my React apps to deploy them to production.</p>
<p>I could build a trivial React example app but it wouldn't be very helpful. So I decided to hook my app into a <a target="_blank" href="https://fred.stlouisfed.org/docs/api/fred/">real API provided by FED St. Louis</a>. The API requires an access key to retrieve data, and endpoints are protected against cross domain requests — no external web app will be able to directly consume data. </p>
<p><strong>Take note</strong>: If you application relies on <strong>server-side rendering</strong> this is <strong>not the right</strong> deployment strategy. You can get inspired but you will still need some sort of backend.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>It is critical to have some basic knowledge of how to build React apps. You should also know some Docker fundamentals before you follow the instructions in this article.  </p>
<p>If you miss anything, don't worry! Just check out this amazing article and YouTube tutorial on FreeCodeCamp:</p>
<ul>
<li><a target="_blank" href="https://www.freecodecamp.org/news/a-beginner-friendly-introduction-to-containers-vms-and-docker-79a9e3e119b/">A Beginner-Friendly Introduction to Containers, VMs and Docker</a> by <a target="_blank" href="https://twitter.com/iam_preethi">@iam_preethi</a></li>
<li><a target="_blank" href="https://www.freecodecamp.org/news/create-react-app-crash-course/">Create React App Crash Course</a></li>
</ul>
<h2 id="heading-how-to-build-an-example-react-app">How to Build an Example React App</h2>
<p>I bootstrapped a simple web app using <em>create-react-app</em>. The only job the app has is displaying a line chart with a representation of the GDP of the United States.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/fredapp.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>The app retrieves data only from the following API:</p>
<pre><code>https:<span class="hljs-comment">//api.stlouisfed.org/fred/series/observations?series_id=GDPCA&amp;frequency=a&amp;observation_start=1999-04-15&amp;observation_end=2021-01-01&amp;file_type=json&amp;api_key=abcdefghijklmnopqrstuvwxyz123456</span>
</code></pre><p>Here are the parameters:</p>
<ul>
<li><code>series_id</code> - The id for a series. The <code>GDPCA</code> stands for the "Real GDP".</li>
<li><code>frequency</code> - The aggregation of the data. The <code>a</code> stands for annual.</li>
<li><code>observation_start</code> - The start of the observation period.</li>
<li><code>observation_end</code> - The end of the observation period.</li>
<li><code>file_type</code> -  The format of the data. Default is <code>xml</code>.</li>
<li><code>api_key</code> - The access key required to retrieve any data from this API. You can request <a target="_blank" href="https://fred.stlouisfed.org/docs/api/api_key.html">one here</a>.</li>
</ul>
<p>You can find more details in the <a target="_blank" href="https://fred.stlouisfed.org/docs/api/fred/series_observations.html">documentation</a>.</p>
<p>Life isn't always perfect, and the API design is not ideal. It requires the developer to pass the access key and expected output of the data as URL parameters. </p>
<p>Passing the output as a parameter is not a problem for us because it only adds some noise - but the leaking API key is. Imagine if somebody intercepts them and abuses the API to perform some prohibited action. We don't want to risk it.</p>
<p>Let's assume for a moment that the API keys are not a problem. Still, it isn't possible to take advantage of this API. The FRED API is protected against cross-domain requests so that we will get the following errors if we try to call it from an external domain:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/frederror.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Many developers would suggest building middleware (a backend) to proxy requests to the API and filter sensitive data. They would say they might need to add new features in the future, and to a certain degree, it is a fair approach. </p>
<p>But I prefer to build my apps in a more <a target="_blank" href="https://en.wikipedia.org/wiki/You_aren%27t_gonna_need_it">YAGNI way (You Ain't Gonna Need It)</a>. So that I'm going to avoid build the backend until it is necessary - in our case I will not build it at all.</p>
<h2 id="heading-lets-use-nginx">Let's use NGINX!</h2>
<p>I am a big fan of NGINX because it brings simplicity with it. NGINX has all you need to prepare a production-grade web server such as HTTP2, compression, TLS, and many other features. </p>
<p>The most important thing is that we can achieve all this by defining a few lines of configuration. Just take a look at the snippet below:</p>
<pre><code class="lang-/container/etc/nginx/nginx.conf">...

http {
    ...

    server {
        ...

        location /api {
            set         $args   $args&amp;&amp;file_type=json&amp;api_key=abcdefghijklmnopqrstuvwxyz123456;
            proxy_pass  https://api.stlouisfed.org/fred/series;
        }
    }
}
</code></pre>
<p>Those 4 lines are all I needed to hide our API key and suppress the CORS errors. Literally! From now on, all HTTP requests to <code>/api</code> will be proxied to FRED API, and only our apps will be able to consume the API. All external requests will face CORS errors.</p>
<blockquote>
<p>To get rid of clutter, I replaced all default content of the file with <code>...</code> (three dots). You can find the full version on my <strong>GitHub</strong> or <strong>video</strong> (links below).</p>
</blockquote>
<p>And this is how our endpoint looks:</p>
<pre><code>/api/observations?series_id=GDPCA&amp;frequency=a&amp;observation_start=<span class="hljs-number">1999</span><span class="hljs-number">-04</span><span class="hljs-number">-15</span>&amp;observation_end=<span class="hljs-number">2021</span><span class="hljs-number">-01</span><span class="hljs-number">-01</span>
</code></pre><p>We need to pass neither the <code>api_key</code> nor <code>file_type</code> parameters to retrieve data. And nobody can read the access key from the URL, so it is safe.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/Screen-Shot-2021-03-14-at-12.49.55.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-docker-loves-nginx">Docker loves NGINX</h2>
<p>The most convenient way to run NGINX in the cloud is to use Docker. For this part, I assume that you know what Docker is (but if not please read the article linked in the prerequisites).</p>
<p>We just need to create a Dockerfile with the following contents:</p>
<pre><code class="lang-dockerfile"><span class="hljs-keyword">FROM</span> nginx

<span class="hljs-keyword">COPY</span><span class="bash"> container /</span>
<span class="hljs-keyword">COPY</span><span class="bash"> build /usr/share/nginx/html</span>
</code></pre>
<p>And now, only three more steps are needed to run the FRED APP:</p>
<ol>
<li><em>Build the React application</em>. This process generates the <code>build/</code> directory containing static files.</li>
<li><em>Build the Docker image</em>. It will create a runnable Docker image.</li>
<li><em>Publish the Docker image</em> to some repository or <em>run it on the local machine</em>. </li>
</ol>
<p>For now, let's try to run it on our machine.</p>
<pre><code>$ yarn install
$ yarn build
$ docker build -t msokola/fred-app:latest .
$ docker run -p <span class="hljs-number">8081</span>:<span class="hljs-number">80</span> -it msokola/fred-app:latest
</code></pre><p>The <code>8081</code> is a port on your machine. It means the app will be available under the following URL:  <code>http://localhost:8081</code>. </p>
<p>After opening this URL in the browser you should see logs like this in your terminal:</p>
<pre><code><span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span> - - [<span class="hljs-number">11</span>/Mar/<span class="hljs-number">2021</span>:<span class="hljs-number">18</span>:<span class="hljs-number">57</span>:<span class="hljs-number">50</span> +<span class="hljs-number">0000</span>] <span class="hljs-string">"GET / HTTP/1.1"</span> <span class="hljs-number">200</span> <span class="hljs-number">1556</span> <span class="hljs-string">"-"</span> <span class="hljs-string">"Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.192 Safari/537.36"</span> <span class="hljs-string">"-"</span>
...
<span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span> - - [<span class="hljs-number">11</span>/Mar/<span class="hljs-number">2021</span>:<span class="hljs-number">18</span>:<span class="hljs-number">57</span>:<span class="hljs-number">51</span> +<span class="hljs-number">0000</span>] <span class="hljs-string">"GET /api/observations?series_id=GDPCA&amp;frequency=a&amp;observation_start=1999-04-15&amp;observation_end=2021-01-01 HTTP/1.1"</span> <span class="hljs-number">200</span> <span class="hljs-number">404</span> <span class="hljs-string">"http://localhost:8081/"</span> <span class="hljs-string">"Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.192 Safari/537.36"</span> <span class="hljs-string">"-"</span>
</code></pre><p>Pay attention to those <code>200</code>s as they stand for HTTP status OK. If you see a <code>400</code> next to the API request it means something is wrong with your API key. The <code>304</code> is also fine (it means the data was cached).</p>
<h2 id="heading-how-to-deploy-the-container-on-aws">How to Deploy the Container on AWS</h2>
<p>The container is working, so we can deploy it. In this part of the article, I am going to show you how to run your application in Amazon Web Services (AWS). </p>
<p>AWS is one of the most popular cloud platforms. If you want to use Microsoft Azure or any other platform, the steps will be similar but the syntax of the commands will differ.</p>
<p><strong>Take note:</strong> I recorded a YouTube video so you can watch me going through the complete deployment process. If you get stuck or encounter any issues, you can check if we have the same results at each step. If you want to watch the video, <a target="_blank" href="https://youtu.be/bUSXeQ4H20g">click here</a> or you can find it embedded in the <em>Summary</em> below.</p>
<h3 id="heading-1-install-aws-cli-tools">1. Install AWS CLI tools</h3>
<p>Before we get started you will need to install the <a target="_blank" href="https://aws.amazon.com/cli/">AWS CLI</a> tools, so you can invoke commands on your cloud. </p>
<p>AWS offers installation wizards for all operating systems, so I am going to skip this section. After a successful installation you have to login by typing the following command:</p>
<pre><code class="lang-bash">$ aws configure
AWS Access Key ID [None]: AKIAIOSFODNN7EXAMPLE
AWS Secret Access Key [None]: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
Default region name [None]: us-east-2
Default output format [None]: json
</code></pre>
<p>To generate access keys, you need to log in to your AWS Console. There, click on your username, and select "<em>My Security Credentials</em>".</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/Screenshot-2021-03-16-at-22.27.53-1.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h3 id="heading-2-create-a-new-elastic-container-registry-ecr">2. Create a new Elastic Container Registry (ECR)</h3>
<p>Once the CLI tools are set up, we will need to create a space where we can store the executables of our application. We use Docker, so our executables will be Docker images which we will run on virtual machines. </p>
<p>AWS offers a dedicated service for storing images called the Elastic Container Registry. The following command will create one for us:</p>
<pre><code class="lang-bash">aws ecr create-repository --repository-name react-to-aws --region us-east-2
</code></pre>
<p>Here are the parameters:</p>
<ul>
<li><code>ecr</code> - The acronyms of "Elastic Container Registry".</li>
<li><code>repository-name</code> - The name of our registry. Please have in mind we will be referring to this name later.</li>
<li><code>region</code> - The region code. You can find a region closest to your location to reduce latency. Here's a <a target="_blank" href="https://docs.aws.amazon.com/general/latest/gr/rande.html">list of all regions</a>.</li>
</ul>
<p>You can find more details in the <a target="_blank" href="https://docs.aws.amazon.com/cli/latest/reference/ecr/create-repository.html">documentation</a>.</p>
<p>And here's the expected output:</p>
<pre><code class="lang-file.json">{
    "repository": {
        "repositoryArn": "arn:aws:ecr:us-east-2:1234567890:repository/react-to-aws2",
        "registryId": "1234567890",
        "repositoryName": "react-to-aws",
        "repositoryUri": "1234567890.dkr.ecr.us-east-2.amazonaws.com/react-to-aws2",
        "createdAt": "2021-03-16T22:50:23+04:00",
        "imageTagMutability": "MUTABLE",
        "imageScanningConfiguration": {
            "scanOnPush": false
        },
        "encryptionConfiguration": {
            "encryptionType": "AES256"
        }
    }
}
</code></pre>
<h3 id="heading-3-push-docker-images-to-the-cloud">3. Push Docker Images to the Cloud</h3>
<p>In this step, we are going to push our Docker images into the cloud. We can do it by copying the push commands from our AWS Console. </p>
<p>Let's open <em>AWS Console</em> in the browser, and click on <em>Elastic Container Registry</em> from the "<em>All Services - Containers</em>" list. If you didn't change your region you can <a target="_blank" href="https://us-east-2.console.aws.amazon.com/ecr/repositories?region=us-east-2">just click here</a>. You are going to see the full list of your repositories:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/Screenshot-2021-03-16-at-23.00.24-1.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Now you need to select the <code>react-to-aws</code> repository, and then "<em>View push commands</em>" from the menu (marked with red circles in the image above). You are going to see the following window:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/Screenshot-2021-03-16-at-23.08.49.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>You need to copy all commands from the modal into your terminal. <strong><em>Do not</em> copy commands from the snippet below</strong> because it won't work.</p>
<pre><code class="lang-bash">$ aws ecr get-login-password --region us-east-2 | docker login --username AWS --password-stdin 123456789.dkr.ecr.us-east-2.amazonaws.com
Login Succeeded

$ docker build -t react-to-aws .
[+] Building 0.6s (8/8) FINISHED
...

$ docker tag react-to-aws:latest 123465789.dkr.ecr.us-east-2.amazonaws.com/react-to-aws:latest

$ docker push 123456789.dkr.ecr.us-east-2.amazonaws.com/react-to-aws:latest
The push refers to repository [123456789.dkr.ecr.us-east-2.amazonaws.com/react-to-aws:latest]
...
latest: digest: sha256:3921262a91fd85d2fccab1d7dbe7adcff84f405a3dd9c0e510a20d744e6c3f74 size: 1988
</code></pre>
<p>Now you can close the modal, and click on the name of the repository (<code>react-to-aws</code>) to browse the list of available images. You should see the following screen:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/Screenshot-2021-03-16-at-23.17.56-1.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Your application is in the repository, ready for deployment! Now click on "<em>Copy URI"</em> <strong>and keep the content of your clipboard</strong> (paste it in some notepad or text file), as we will need to run it!</p>
<h3 id="heading-4-configure-the-application">4. Configure the Application</h3>
<p>Our image is available in the cloud, so now we need to configure it. </p>
<p>Virtual machines don't know how to execute your image to ensure it works well. We must define some instructions such as open ports, environment variables, and so on. AWS calls it task definition.</p>
<p>Open <em>AWS Console</em>, and click on <em>Elastic Container Service (ECS)</em> from the "<em>All Services - Containers</em>" list. If you didn't change your region you can <a target="_blank" href="https://us-east-2.console.aws.amazon.com/ecs/home?region=us-east-2#/clusters">click here</a>. </p>
<p>Now select <em>Task Definitions</em>, and click on "<em>Create new Task Definition</em>" as marked in the image below:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/Screenshot-2021-03-17-at-00.07.54-1.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>We have two options for running our task: <code>FARGATE</code> and <code>EC2</code>. Choose <code>FARGATE</code><em>,</em> and click "<em>Next step</em>".</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/Screenshot-2021-03-17-at-00.09.53.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>In the next step, you need to fill out the form with the following values:</p>
<ul>
<li><strong>Task Definition Name</strong> - <code>react-to-aws-task</code>.</li>
<li><strong>Task Role</strong> - <code>none</code>.</li>
<li><strong>Task memory (GB)</strong> - <code>0.5GB</code> (the smallest).</li>
<li><strong>Task CPU (vCPU)</strong> - <code>0.25 vCPU</code> (the smallest).</li>
</ul>
<p>Once you've reached the "<em>Container Definitions"</em> section click <em>"Add container"</em>:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/Screenshot-2021-03-17-at-00.18.19.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Fill out the form with the following values:</p>
<ul>
<li><strong>Container Name</strong> - <code>react-to-aws</code>.</li>
<li><strong>Image</strong> - The URI from the step 4. You've pasted it somewhere.</li>
<li><strong>Memory Limits (MiB)</strong>- <code>Soft limit</code> <code>128</code>.</li>
<li><strong>Port mappings</strong> - <code>80</code> - the HTTP port.</li>
</ul>
<p>Other options aren't relevant for us. Now click on the "<em>Add"</em> button to add a container, and finish the task definition by clicking <em>Create</em>. You should see the following screen, and click on "<em>View task definition</em>".</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/Screenshot-2021-03-17-at-00.24.56.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h3 id="heading-5-lets-run-it">5. Let's run it!</h3>
<p>Finally, we can create a cluster, so we can run our application in the cloud. You need to select "<em>Clusters</em>" from the menu on the left-hand side, and "<em>Create Cluster</em>". As shown in the image below:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/Screenshot-2021-03-17-at-00.29.46.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Now we have three options: <code>Networking only</code>, <code>EC2 Linux + Networking</code>, and <code>EC2 Windows + Networking</code>. Choose the first one - <code>Networking only</code>, and click on "<em>Next step</em>". You should see the following screen:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/Screenshot-2021-03-17-at-00.34.35.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Enter the cluster name <code>react-to-aws</code>, and click on the "<em>Create</em>" button. You should see a successful lunch status. It looks similar to the screen we got once our task definition was created. Now click on "<em>View Cluster</em>".</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/Screenshot-2021-03-17-at-00.39.42-1.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Now you need to click on the "<em>Tasks"</em> tab, and click on "<em>Run new Task</em>". Congratulations! You have reached the very last form to fill out :)</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/Screenshot-2021-03-17-at-00.41.10.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Fill out the form with the following values:</p>
<ul>
<li><strong>Launch type</strong> - <code>FARGATE</code>.</li>
<li><strong>Cluster VPC</strong> - The first one.</li>
<li><strong>Subnet</strong> - The first one.</li>
</ul>
<p><strong>Keep the other values as they are</strong>, and click the "<em>Run task</em>" button. You should see the following screen:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/Screenshot-2021-03-17-at-00.46.45-1.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>We will need to wait about a minute until the "<em>Last status</em>" changes into RUNNING. Please have in mind that you need to click the "<em>Refresh</em>" button to refresh the list. Once the task status is running, click on the task name.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/Screenshot-2021-03-17-at-00.50.52.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>In the "<em>Network"</em> section you will find the <em>Public IP</em> of your container. You can open it in your browser, and you will see your application.</p>
<h2 id="heading-summary">Summary</h2>
<p>If you're at the beginning of your career, you might not have ever deployed an application yourself. But it is good to learn that skill, because one day you will need to do so. </p>
<p>Every project needs to face users otherwise it will have no chance of being successful, and will never pay for itself.</p>
<blockquote>
<p>“A ship in harbor is safe — but that is not what ships are built for.”<br>— John A. Shedd</p>
</blockquote>
<p>The configuration process is a bit tedious, but the good news is that you need to do it only once. </p>
<p>After you configure everything, your next deployments will be simpler. You only need to push the new image and restart the task to deploy a new version of your application. </p>
<p>If you are interested in digging deeper into AWS, FreeCodeCamp offers a <a target="_blank" href="https://www.freecodecamp.org/news/learn-the-basics-of-amazon-web-services/">free tutorial</a> on it (~5 hours).</p>
<p>You can find a <strong>screencast of this tutorial (17 minutes)</strong> on my <strong>YouTube</strong> channel. I am at the very beginning of my YouTube journey - at least weekly I upload a video on programming. It would mean the world to me if you watch my screencast, subscribe, and hit the like button :)</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/bUSXeQ4H20g" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<p><strong>You can find all the code in this GitHub repository</strong> : <a target="_blank" href="https://github.com/mateuszsokola/react-to-aws">https://github.com/mateuszsokola/react-to-aws</a></p>
<p>You can DM me on Twitter: <a target="_blank" href="https://twitter.com/msokola">@msokola</a></p>
<p>That is all folks! I hope you liked it and have a great day :)</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/vidar-nordli-mathisen-xgP0GNl9Gzg-unsplash.jpg" alt="Image" width="600" height="400" loading="lazy">
_Photo by [Unsplash](https://unsplash.com/@vidarnm?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText"&gt;Vidar Nordli-Mathisen on &lt;a href="https://www.freecodecamp.org/news/s/photos/ship-storm?utm_source=unsplash&amp;utm_medium=referral&amp;utm<em>content=creditCopyText)</em></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Deploy a Node.js App – From Server Setup to Production ]]>
                </title>
                <description>
                    <![CDATA[ By Yiğit Kemal Erinç In this tutorial, we are going to learn everything we need to know before deploying a Node app to a production server. We will start by renting a server on Digital Ocean. Then we'll configure this server, connect to it, install N... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/deploy-nodejs-app-server-to-production/</link>
                <guid isPermaLink="false">66d45e41182810487e0ce14d</guid>
                
                    <category>
                        <![CDATA[ deployment ]]>
                    </category>
                
                    <category>
                        <![CDATA[ nginx ]]>
                    </category>
                
                    <category>
                        <![CDATA[ node ]]>
                    </category>
                
                    <category>
                        <![CDATA[ servers ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Mon, 01 Mar 2021 21:39:43 +0000</pubDate>
                <media:content url="https://cdn-media-2.freecodecamp.org/w1280/603a54d9a675540a22924662.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Yiğit Kemal Erinç</p>
<p>In this tutorial, we are going to learn everything we need to know before deploying a Node app to a production server.</p>
<p>We will start by renting a server on Digital Ocean. Then we'll configure this server, connect to it, install Nginx and configure it, pull or create our Node app, and run it as a process. </p>
<p>As you can see, there is a lot to do and it will be an action-packed tutorial. So let's get started without wasting any time.</p>
<p>You should have some basic knowledge on how the Terminal works and how to work in Vi/Vim before getting started. If you are not familiar with basic commands, I would advise you to read up on them a bit. </p>
<p>I will run the commands in MacOS. If you want to follow this tutorial in Windows, you can use Powershell or some other Unix emulator of your choice.</p>
<p>Although I will use Node.js as the platform of our example application, most of the steps are the same for any web application.</p>
<h2 id="heading-why-digital-ocean">Why Digital Ocean?</h2>
<p>I choose Digital Ocean because it is cheap and the interface is really easy to use, compared to the likes of AWS. Also, a $100 credit is included in the GitHub student pack so you do not have to pay anything for a couple of months. It is ideal for deploying a course or hobby project.</p>
<p>It has a concept called Droplets, which is basically your share of a server. You can think of the server as an apartment in which you own or rent a flat.</p>
<p>Droplets work with the help of Virtual Machines which run on the server. So a Droplet is your Virtual Machine on a shared server. Since it is a VM, its CPU and memory share can be easily increased, usually by throwing more money at your provider.</p>
<h2 id="heading-how-to-create-a-digital-ocean-project">How to Create a Digital Ocean Project</h2>
<p><img src="https://erinc.io/wp-content/uploads/2021/02/image-3.png" alt="This image has an empty alt attribute; its file name is image-3.png" width="600" height="400" loading="lazy"></p>
<p>I am assuming that you have already signed up and logged in to Digital Ocean before proceeding. We should first create a project that will contain our droplets. Let's click on the new project button on the left side menu. It will ask you to name your project.</p>
<p><img src="https://erinc.io/wp-content/uploads/2021/02/Screen-Shot-2021-02-22-at-13.35.06.png" alt="This image has an empty alt attribute; its file name is Screen-Shot-2021-02-22-at-13.35.06.png" width="600" height="400" loading="lazy"></p>
<p>Enter whatever name you want. It will also ask you if you want to move any resources, but for now just click Skip – we will create the droplet later.</p>
<h2 id="heading-how-to-create-a-droplet-on-digital-ocean">How to Create a Droplet on Digital Ocean</h2>
<p>Let's create our droplet by clicking the Get Started button.</p>
<p><img src="https://erinc.io/wp-content/uploads/2021/02/image-4-1024x593.png" alt="This image has an empty alt attribute; its file name is image-4-1024x593.png" width="600" height="400" loading="lazy"></p>
<p>After clicking the button, it will ask us to choose a VM image.</p>
<p><img src="https://erinc.io/wp-content/uploads/2021/02/Screen-Shot-2021-02-22-at-13.12.43-1024x567.png" alt="This image has an empty alt attribute; its file name is Screen-Shot-2021-02-22-at-13.12.43-1024x567.png" width="600" height="400" loading="lazy">
<em>Choosing an Image</em></p>
<p>On this page, I will select Ubuntu 20.04 since it is the latest LTS version at the time I am writing this post. LTS means "Long Term Support". It is best to go with the LTS version for actual projects, because the provider guarantees that it will be supported and maintained for a long time. This means you will not have problems in the long run.</p>
<p>I have chosen Ubuntu, and would recommend it to you since it is the most commonly used Linux distribution. This means it's also the easiest to find answers to your future questions.</p>
<p>You can also choose to have a Dedicated CPU if you need it. If you are building your own startup or any business project, I would recommend reading this <a target="_blank" href="https://www.digitalocean.com/docs/droplets/resources/choose-plan/">post</a> which contains detailed instructions about how to pick the right option for you.</p>
<p>I will go with the cheapest option in this case. </p>
<p>Then you will need to select a Datacenter region. You should pick the one that is closest to you to minimize network delay.</p>
<p><img src="https://erinc.io/wp-content/uploads/2021/02/image-2-1024x519.png" alt="This image has an empty alt attribute; its file name is image-2-1024x519.png" width="600" height="400" loading="lazy">
<em>Select a Datacenter</em></p>
<p>Next let's select SSH Keys as the Authentication Method, since it is much more secure than basic password authentication. </p>
<p><img src="https://erinc.io/wp-content/uploads/2021/02/image-5-1024x459.png" alt="This image has an empty alt attribute; its file name is image-5-1024x459.png" width="600" height="400" loading="lazy">
<em>Authentication Method</em></p>
<p>To connect to the server we need to generate a new SSH key on our own device and add it to Digital Ocean.</p>
<h2 id="heading-how-to-generate-an-ssh-key">How to Generate an SSH Key</h2>
<p>I will generate the key on my macOS device. If you are using Windows you can refer to <a target="_blank" href="https://phoenixnap.com/kb/generate-ssh-key-windows-10">this</a> article. Open your terminal and move into the ssh folder:</p>
<pre><code>cd ~/.ssh
</code></pre><p>Then create your SSH key:</p>
<pre><code>ssh-keygen
</code></pre><p>If your computer says that it does not know this command, you should install it via brew.</p>
<p><img src="https://erinc.io/wp-content/uploads/2021/02/image-7-1024x140.png" alt="This image has an empty alt attribute; its file name is image-7-1024x140.png" width="600" height="400" loading="lazy"></p>
<p>It will ask you to name the file and enter a passphrase. Do not enter a name, just press enter and go with the defaults. You should have these files generated. I have named mine digital-ocean-ssh in this screenshot, so do not get confused by that.</p>
<pre><code>❯ lsid_dsa      id_rsa      known_hosts
</code></pre><p>Our public key is the <code>id_dsa</code> and the <code>id_rsa</code> is our private key. If you forget which one is private, you can always print one of them to see.</p>
<h2 id="heading-how-to-add-your-ssh-key-to-digital-ocean">How to Add Your SSH Key to Digital Ocean</h2>
<p>Now we want to copy our public key and upload it to Digital Ocean so they will know which key to use in authentication.</p>
<p><img src="https://erinc.io/wp-content/uploads/2021/02/image-9-1024x149.png" alt="This image has an empty alt attribute; its file name is image-9-1024x149.png" width="600" height="400" loading="lazy"></p>
<p>Copy this whole key including the ssh-rsa part.</p>
<p>Click on "New SSH Key":</p>
<p><img src="https://erinc.io/wp-content/uploads/2021/02/image-10.png" alt="This image has an empty alt attribute; its file name is image-10.png" width="600" height="400" loading="lazy"></p>
<p>Paste the key in the textbox that appears after you click the button and you should see your SSH key.</p>
<p><img src="https://erinc.io/wp-content/uploads/2021/02/image-11.png" alt="This image has an empty alt attribute; its file name is image-11.png" width="600" height="400" loading="lazy"></p>
<h2 id="heading-how-to-connect-to-the-server">How to Connect to the Server</h2>
<p>We will use the terminal to connect to our server with SSH. You can also take a look at Termius for a nice interface if you want.</p>
<p>Run this command in your terminal after replacing the IP_ADDRESS with your server's IP address (you can look it up from Digital Ocean's panel).</p>
<pre><code>ssh root@IP_ADDRESS
</code></pre><p>If everything goes well, now you should be in the server's terminal. We have successfully connected to server. If there is any error, you can debug it by running the command with the "-v" option or "-vv" for even more verbosity.</p>
<h2 id="heading-how-to-set-up-the-server">How to Set Up the Server</h2>
<p>We need to do some initial setup before deploying the Node app to the server.</p>
<h3 id="heading-update-and-upgrade-software">Update and Upgrade Software</h3>
<p>We want to update the server's software to make sure we are using the latest versions. </p>
<p>Many servers are vulnerable to attacks because they are using older versions of software with known vulnerabilities. Attackers can search for the vulnerabilities in those software and try to exploit them in order to gain access to your server. </p>
<p>You can update Ubuntu's software using the <strong>"apt update"</strong> command.</p>
<pre><code>apt updateHit:<span class="hljs-number">1</span> https:<span class="hljs-comment">//repos.insights.digitalocean.com/apt/do-agent main InReleaseGet:2 http://mirrors.digitalocean.com/ubuntu focal InRelease [265 kB]      Hit:3 http://mirrors.digitalocean.com/ubuntu focal-updates InRelease                Get:4 http://security.ubuntu.com/ubuntu focal-security InRelease [109 kB]Hit:5 http://mirrors.digitalocean.com/ubuntu focal-backports InReleaseFetched 374 kB in 1s (662 kB/s)                          Reading package lists... DoneBuilding dependency tree       Reading state information... Done96 packages can be upgraded. Run 'apt list --upgradable' to see them.</span>
</code></pre><p>If you read the message, it says that "96 packages can be upgraded". We have installed the new software packages but we have not upgraded our software to those versions yet. </p>
<p>To do that, let's run another command:</p>
<pre><code>apt upgrade
</code></pre><p>Type y when it prompts you and it will upgrade the software.</p>
<h3 id="heading-create-a-user">Create a User</h3>
<p>We have connected to the server as the root user (the user with the highest privileges). Being the root is dangerous and can open us up to vulnerabilities. </p>
<p>Therefore we should create a new user and not run commands as root. Replace <code>$username</code> with a username of your choice.</p>
<pre><code>whoamiroot
</code></pre><pre><code>adduser $username
</code></pre><p>You need to enter a password for the user. After that point, it will ask a bunch of questions, so just input y until the prompting is over.</p>
<p>The new user has been created but we also need to add this new user to the "sudo" group so that we can perform any action we need.</p>
<pre><code>usermod -aG sudo $USERNAME
</code></pre><p>We add group with the <code>-aG</code> (add group) option, and we add the group name <code>sudo</code> to our username.</p>
<p>We are still root, so let's switch our user to the newly created user, using the <code>su</code> (switch user) command.</p>
<pre><code>su $USERNAME
</code></pre><p>After this point, if you run <strong><code>whoami</code></strong> command, you should see your username. You can confirm the existence of the sudo group by running this command:</p>
<pre><code>sudo cat /<span class="hljs-keyword">var</span>/log/auth.log
</code></pre><p>Only superusers can view this file and OS will ask for your user password after you run this command.</p>
<h3 id="heading-copy-the-ssh-key">Copy the SSH Key</h3>
<p>We have successfully created the user but we have not enabled SSH login for this new user yet. </p>
<p>Therefore, we have to copy the public key that we previously created on our local computer and paste it into this user's SSH folder so SSH can know which key should it use to authenticate our new user.</p>
<pre><code>mkdir -p ~/.ssh
</code></pre><p>The <code>-p</code> argument creates the directory if it does not exist.</p>
<pre><code>vi ~<span class="hljs-regexp">/.ssh/</span>authorized_keys
</code></pre><p>We will use vi or vim to create a file and call it <strong><code>authorized_keys</code></strong>.</p>
<p>Copy your public key (<code>id_dsa</code> file) then press "i" to go into insert mode. Then just paste it into this file with CMD + V.</p>
<p>Press esc to quit insert mode, type <strong>:wq</strong> to save and quit.</p>
<p>If you have any problems about using Vim-Vi, you can check out <a target="_blank" href="https://www.freecodecamp.org/news/how-not-to-be-afraid-of-vim-anymore-ec0b7264b0ae/">one of the many tutorials</a> that explain how to use it.</p>
<h3 id="heading-connect-to-server-as-new-user">Connect to Server as New User</h3>
<p>Now we should be able to connect to the server without any problems using ssh. You can use this command to connect, just remember to insert your username and <code>IP_ADDRESS</code>.</p>
<pre><code>ssh $USERNAME@IP_ADDRESS
</code></pre><p>If you are having any problems at this point, you should just delete the droplet and start over. It does not take a lot of time to start over but debugging server problems can be difficult.</p>
<h3 id="heading-how-to-disable-root-login">How to Disable Root Login</h3>
<p>It is a good practice to disable Root login as a security precaution, so let's do that now.</p>
<p>It can be useful to change the file permission just in case so that we won't run into problems regarding permissions in the future.</p>
<pre><code>chmod <span class="hljs-number">644</span> ~<span class="hljs-regexp">/.ssh/</span>authorized_keys
</code></pre><p>Let's now open our <code>sshd_config</code> file:</p>
<pre><code>sudo vi /etc/ssh/sshd_config
</code></pre><p>Find this line and change the yes to no in the same way we did earlier with vi.</p>
<pre><code>PermitRootLogin no
</code></pre><p>Save and quit vi.</p>
<h2 id="heading-how-to-install-nodejs-and-git">How to Install Node.js and Git</h2>
<p>We can now go ahead and install Node.js and Git:</p>
<pre><code>sudo apt install nodejs npm
</code></pre><pre><code>sudo apt install git
</code></pre><p>We are now ready to create a Node app and run it. You can either pull your Node project from Github or create a Node app here just to test if it works.</p>
<p>Move to a directory of your choice and create an <strong>"app.js"</strong> file:</p>
<pre><code>sudo vi app.js
</code></pre><p>You can paste the following snippet into your <strong>app.js</strong> file:</p>
<pre><code><span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">'express'</span>);<span class="hljs-keyword">const</span> app = express();<span class="hljs-keyword">const</span> port = <span class="hljs-number">3000</span>;app.get(<span class="hljs-string">'/'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {        res.send(<span class="hljs-string">'Hello World'</span>);});app.listen(port, <span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Example app listening on port <span class="hljs-subst">${port}</span>!`</span>));
</code></pre><p>Now we can run it with the command:</p>
<pre><code>node app.js
</code></pre><p>You should see "Example app listening on port 3000!" on your terminal.</p>
<p>We can confirm that it is working by sending a request to our server:</p>
<pre><code>GET http:<span class="hljs-comment">//IP_ADDRESS:3000/</span>
</code></pre><p>Send this request either from an HTTP client like Postman or your browser and you should see the "Hello World" message.</p>
<p>At this point, you should notice that something is wrong: Regular users do not know how to send requests to port 3000.</p>
<p>We should redirect the requests that come to our web server from our IP to port 3000. We can accomplish this with the help of Nginx.</p>
<p><img src="https://erinc.io/wp-content/uploads/2021/02/image-16.png" alt="This image has an empty alt attribute; its file name is image-16.png" width="600" height="400" loading="lazy"></p>
<h2 id="heading-how-to-install-and-configure-nginx">How to Install and Configure Nginx</h2>
<p>We will use Nginx as a Reverse Proxy to redirect the requests to our Node app.</p>
<p><img src="https://erinc.io/wp-content/uploads/2021/02/image-14-1024x531.png" alt="This image has an empty alt attribute; its file name is image-14-1024x531.png" width="600" height="400" loading="lazy">
<em>Nginx as a Reverse Proxy</em></p>
<p>Let's install Nginx:</p>
<pre><code>sudo apt install nginx
</code></pre><p>Start the Nginx service:</p>
<pre><code>sudo service nginx start
</code></pre><p>We can test to see if it is working by sending a request to our server's IP address from the browser. Type your server's IP address to your browser and you should see this:</p>
<p><img src="https://erinc.io/wp-content/uploads/2021/02/image-15-1024x231.png" alt="This image has an empty alt attribute; its file name is image-15-1024x231.png" width="600" height="400" loading="lazy"></p>
<p>It is important to know that Nginx serves from <strong>"/var/www/html"</strong> by default and you can find this HTML file in that directory as well.</p>
<p>I also advise you to create a folder under "/var/www", call it app, and move your Node app to that folder so it will be easy to find.</p>
<h3 id="heading-how-to-configure-the-nginx-reverse-proxy">How to Configure the Nginx Reverse Proxy</h3>
<p>We will edit the Nginx config file to configure a reverse proxy:</p>
<pre><code>sudo vi /etc/nginx/sites-available/<span class="hljs-keyword">default</span>
</code></pre><p>In this file you need to find the location / block and change it as follows:</p>
<pre><code>location / {                # First attempt to serve request <span class="hljs-keyword">as</span> file, then                # <span class="hljs-keyword">as</span> directory, then fall back to displaying a <span class="hljs-number">404.</span>                proxy_pass http:<span class="hljs-comment">//127.0.0.1:3000/;        }</span>
</code></pre><p>The <code>proxy_pass</code> directive proxies the request to a specified port. We give the port that our Node application is running on.</p>
<p>Let's restart Nginx so the changes can take effect:</p>
<pre><code>sudo service nginx reload
</code></pre><p>After this step, we should be able to see the message when we send a request to our server. Congratulations, we have completed the minimum number of steps to deploy a Node app! </p>
<p><img src="https://erinc.io/wp-content/uploads/2021/02/Screen-Shot-2021-02-24-at-01.10.33-1024x67.png" alt="This image has an empty alt attribute; its file name is Screen-Shot-2021-02-24-at-01.10.33-1024x67.png" width="600" height="400" loading="lazy"></p>
<p>But I still advise you to complete the following bonus step as well, as I believe it's quite important.</p>
<p>If you can't see the hello world message, you can check if your app and Nginx are running and restart them.</p>
<h2 id="heading-how-to-run-your-app-as-a-process">How to Run your App as a Process</h2>
<p>We do not want to start our application manually every time something goes wrong and our app crashes. We want it to restart on its own. Also, whenever the server starts, our app should start too. </p>
<p>To make this happen, we can use PM2. Let's install PM2 and configure it.</p>
<pre><code>sudo npm i -g pm2
</code></pre><p>We are installing pm2 globally by using the "-g" option so that it will be accessible from every folder.</p>
<pre><code>pm2 start app.js
</code></pre><p>This makes sure that the app will restart if it exits due to an error.</p>
<p>Let's save the current process list.</p>
<pre><code>pm2 save
</code></pre><p>We also need to convert it to a daemon that runs whenever the system starts:</p>
<pre><code>pm2 startup systemd
</code></pre><p><img src="https://erinc.io/wp-content/uploads/2021/02/image-17.png" alt="This image has an empty alt attribute; its file name is image-17.png" width="600" height="400" loading="lazy"></p>
<p>As a reminder, in this tutorial, I'm using the commands for Ubuntu. If you are using any other Linux distro, you should replace <code>systemd</code> in this command.</p>
<p>We can confirm that the service is getting restarted by rebooting the server and sending a request without running app.js by hand:</p>
<pre><code>sudo reboot
</code></pre><p>After sending a request as we did earlier, you should be able to see the hello world message.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this tutorial we started from scratch, rented a server for ourselves, connected to it, and configured it in a way that it serves our Node.js app from port 80. </p>
<p>If you have followed along and were able to complete all steps, congratulations! You can be proud of yourself, as this was not the easiest topic :). I hope that you have learned a lot. Thank you for your time.</p>
<p>I am planning to explore this topic further by connecting the server to a domain name, then connecting it to CircleCI for continuous integration. I'll also go through the required steps to make your Node.js/React app production ready. This post had already gotten long enough, though, so those topics are reserved for another post :)</p>
<p>If you have enjoyed reading and want to get informed about my future posts, you can subscribe to my <a target="_blank" href="https://erinc.io/">personal blog</a>. You can see my previous posts there if you are interested in reading more. I usually write about web development-related topics.  </p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Setup DNS for a Website Using Kubernetes, EKS, and NGINX ]]>
                </title>
                <description>
                    <![CDATA[ By Adam Henson As the creator of Foo, a platform for website quality monitoring, I recently endeavored in a migration to Kubernetes and EKS (an AWS service). Kubernetes provides a robust level of DNS support. Luckily for us, within a cluster, we can ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-setup-dns-for-a-website-using-kubernetes-eks-and-nginx/</link>
                <guid isPermaLink="false">66d45d5dbd438296f45cd37d</guid>
                
                    <category>
                        <![CDATA[ Devops ]]>
                    </category>
                
                    <category>
                        <![CDATA[ dns ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Kubernetes ]]>
                    </category>
                
                    <category>
                        <![CDATA[ nginx ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Thu, 07 May 2020 11:30:00 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2020/05/nyc.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Adam Henson</p>
<p>As the creator of <a target="_blank" href="https://www.foo.software/">Foo, a platform for website quality monitoring</a>, I recently endeavored in a migration to Kubernetes and EKS (an AWS service).</p>
<p>Kubernetes provides a robust level of DNS support. Luckily for us, within a cluster, we can reference pods by host name as defined in a spec. </p>
<p>But what if we want to expose an app to the outside world as a website under a static domain? I thought this would be a common, well documented case, but boy was I wrong.</p>
<blockquote>
<p>Assume a Service named <code>foo</code> in the Kubernetes namespace <code>bar</code>. A Pod running in namespace <code>bar</code> can look up this service by simply doing a DNS query for <code>foo</code>. A Pod running in namespace <code>quux</code> can look up this service by doing a DNS query for <code>foo.bar</code> ~ <a target="_blank" href="https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/">DNS for Services and Pods - Kubernetes</a></p>
</blockquote>
<p>Yes, that's great ❤️ But this still leads to many unsolved mysteries. Let's take this one step at a time shall we?! This post will address the following items.</p>
<ol>
<li><strong>How to define services</strong></li>
<li><strong>How to expose multiple services under one NGINX server</strong>. No fancy schmancy "<a target="_blank" href="https://kubernetes.io/docs/concepts/services-networking/ingress/">Ingress</a>" needed <strong>?</strong></li>
<li><strong>How to create an external DNS and connect to a domain</strong> you've acquired through any qualified registry like GoDaddy or Google Domains, for example. We'll use <a target="_blank" href="https://aws.amazon.com/route53/">Route 53</a> and <a target="_blank" href="https://github.com/kubernetes-sigs/external-dns">ExternalDNS</a> to do the heavy lifting.</li>
</ol>
<p>This post assumes a setup with EKS and <code>eksctl</code> as documented in "<a target="_blank" href="https://docs.aws.amazon.com/eks/latest/userguide/getting-started-eksctl.html">Getting started with <code>eksctl</code></a>", but many of the concepts and examples in this post could be applicable in a variety of configurations.</p>
<h2 id="heading-step-1-define-services">Step 1: Define Services</h2>
<p><a target="_blank" href="https://kubernetes.io/docs/concepts/services-networking/connect-applications-service/">Connecting Applications with Services</a> explains how to expose an NGINX application by defining a <code>Deployment</code> and <code>Service</code>. Let's go ahead and create 3 applications in the same manner: a user facing web app, an API and a reverse proxy NGINX server to expose the two apps under one host.</p>
<blockquote>
<p>web-deployment.yaml</p>
</blockquote>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">apps/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Deployment</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">web</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">matchLabels:</span>
      <span class="hljs-attr">app:</span> <span class="hljs-string">web</span>
  <span class="hljs-attr">template:</span>
    <span class="hljs-attr">metadata:</span>
      <span class="hljs-attr">labels:</span>
        <span class="hljs-attr">app:</span> <span class="hljs-string">web</span>
    <span class="hljs-attr">spec:</span>
      <span class="hljs-attr">containers:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">web</span>
        <span class="hljs-comment"># etc, etc</span>
</code></pre>
<blockquote>
<p>web-service.yaml</p>
</blockquote>
<pre><code>apiVersion: v1
<span class="hljs-attr">kind</span>: Service
<span class="hljs-attr">metadata</span>:
  name: web
  <span class="hljs-attr">labels</span>:
    app: web
<span class="hljs-attr">spec</span>:
  ports:
  - name: <span class="hljs-string">"3000"</span>
    <span class="hljs-attr">port</span>: <span class="hljs-number">3000</span>
    <span class="hljs-attr">targetPort</span>: <span class="hljs-number">3000</span>
  <span class="hljs-attr">selector</span>:
    app: web
</code></pre><blockquote>
<p>api-deployment.yaml</p>
</blockquote>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">apps/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Deployment</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">api</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">replicas:</span> <span class="hljs-number">1</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">matchLabels:</span>
      <span class="hljs-attr">app:</span> <span class="hljs-string">api</span>
  <span class="hljs-attr">template:</span>
    <span class="hljs-attr">metadata:</span>
      <span class="hljs-attr">labels:</span>
        <span class="hljs-attr">app:</span> <span class="hljs-string">api</span>
    <span class="hljs-attr">spec:</span>
      <span class="hljs-attr">containers:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">api</span>
        <span class="hljs-comment"># etc, etc</span>
</code></pre>
<blockquote>
<p>api-service.yaml</p>
</blockquote>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Service</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">api</span>
  <span class="hljs-attr">labels:</span>
    <span class="hljs-attr">app:</span> <span class="hljs-string">api</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">ports:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">"3000"</span>
    <span class="hljs-attr">port:</span> <span class="hljs-number">3000</span>
    <span class="hljs-attr">targetPort:</span> <span class="hljs-number">3000</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">app:</span> <span class="hljs-string">api</span>
</code></pre>
<p>Fair enough, let's move on!</p>
<h2 id="heading-step-2-expose-multiple-services-under-one-nginx-server">Step 2: Expose Multiple Services Under One NGINX Server</h2>
<p>NGINX is a reverse proxy in that it proxies a request by sending it to a specified origin, fetches the response, and sends it back to the client. </p>
<p>Going back to the bit about service names being accessible to other pods in a cluster, we can setup an NGINX configuration to look something like this.</p>
<blockquote>
<p>sites-enabled/www.example.com.conf</p>
</blockquote>
<pre><code>upstream api {
  server api:<span class="hljs-number">3000</span>;
}

upstream web {
  server web:<span class="hljs-number">3000</span>;
}

server {
  listen <span class="hljs-number">80</span>;

  server_name www.example.com;

  location / {
    proxy_pass http:<span class="hljs-comment">//web;</span>
  }

  location /api {
    proxy_pass http:<span class="hljs-comment">//api;</span>
  }
}
</code></pre><p>Note how we can reference origin hosts like <code>web:3000</code> and <code>api:300</code>. Niiiice!</p>
<blockquote>
<p>nginx-deployment.yaml</p>
</blockquote>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">apps/v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Deployment</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">nginx</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">matchLabels:</span>
      <span class="hljs-attr">app:</span> <span class="hljs-string">nginx</span>
  <span class="hljs-attr">template:</span>
    <span class="hljs-attr">metadata:</span>
      <span class="hljs-attr">labels:</span>
        <span class="hljs-attr">app:</span> <span class="hljs-string">nginx</span>
    <span class="hljs-attr">spec:</span>
      <span class="hljs-attr">containers:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">nginx</span>
        <span class="hljs-attr">image:</span> <span class="hljs-string">nginx</span>
        <span class="hljs-attr">ports:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">containerPort:</span> <span class="hljs-number">80</span>
</code></pre>
<blockquote>
<p>nginx-service.yaml</p>
</blockquote>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Service</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">nginx</span>
  <span class="hljs-attr">annotations:</span>
    <span class="hljs-comment"># this part will make more sense later</span>
    <span class="hljs-attr">external-dns.alpha.kubernetes.io/hostname:</span> <span class="hljs-string">www.example.com</span>
  <span class="hljs-attr">labels:</span>
    <span class="hljs-attr">app:</span> <span class="hljs-string">nginx</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">type:</span> <span class="hljs-string">LoadBalancer</span>
  <span class="hljs-attr">ports:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">"80"</span>
    <span class="hljs-attr">port:</span> <span class="hljs-number">80</span>
    <span class="hljs-attr">targetPort:</span> <span class="hljs-number">80</span>
  <span class="hljs-attr">selector:</span>
    <span class="hljs-attr">app:</span> <span class="hljs-string">nginx</span>
</code></pre>
<p>...and, we're done! Right? In my experience, initially I thought so. The <code>[LoadBalancer](https://kubernetes.io/docs/tasks/access-application-cluster/create-external-load-balancer/)</code> provides an externally-accessible IP. You can confirm by running <code>kubectl get svc</code> and sure enough you'll find a host name listed in the <code>EXTERNAL-IP</code> column. </p>
<p>Assuming you've acquired a domain from a provider that offers an interface to manage DNS settings, you could simply add this URL as a <code>CNAME</code> and you're good, right? Well, kinda... but not so much.</p>
<p>Kubernetes Pods are considered to be relatively ephemeral (rather than durable) entities. Find more on this in "<a target="_blank" href="https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/">Pod Lifecycle - Kubernetes</a>". </p>
<p>With that said, anytime a significant change has been made in the lifecycle of a service, in our case the NGINX app, we will have a different IP address which will in turn cause significant downtime in our app which defeats a main purpose of Kubernetes - to help establish a "highly available" application. </p>
<p>Okay, don't panic - we'll get through this ?</p>
<h2 id="heading-step-3-create-an-external-dns-service-to-dynamically-point-nginx">Step 3: Create an External DNS Service to Dynamically Point NGINX</h2>
<p>In the previous step, with our <code>LoadBalancer</code> spec coupled with EKS we actually created an <a target="_blank" href="https://aws.amazon.com/elasticloadbalancing/">Elastic Load Balancer</a> (for better or worse). </p>
<p>In this section we'll create a DNS service that points our load balancer via "ALIAS record". This ALIAS record is essentially dynamic in that a new one is created whenever our service changes. The stability is established in the name server records.</p>
<p>The tl;dr for the remaining portion is simply follow the <a target="_blank" href="https://github.com/kubernetes-sigs/external-dns/blob/master/docs/tutorials/aws.md">documentation for using ExternalDNS with Route 53</a>. Route 53 is "<a target="_blank" href="https://aws.amazon.com/route53/">cloud Domain Name System (DNS) web service</a>". </p>
<p>Below were things I had to do that weren't obvious from the documentation. Hold on to your horses, this gets a little scrappy.</p>
<ul>
<li><code>eksctl utils associate-iam-oidc-provider --cluster=your-cluster-name</code> per <a target="_blank" href="https://eksctl.io/usage/iamserviceaccounts/"><code>eksctl</code> service accounts documentation</a>.</li>
<li>When creating the IAM policy document per the <a target="_blank" href="https://github.com/kubernetes-sigs/external-dns/blob/master/docs/tutorials/aws.md#iam-policy">ExternalDNS documentation</a>, I actually had to do it via CLI vs online in my account. I kept getting this error: <code>WebIdentityErr: failed to retrieve credentials\ncaused by: AccessDenied: Not authorized to perform sts:AssumeRoleWithWebIdentity\n\tstatus code: 403</code>. When I created the policy via CLI the issue went away. Below is the full command you should be able to literally copy and execute if you have the <a target="_blank" href="https://aws.amazon.com/cli/">AWS CLI</a> installed.</li>
</ul>
<pre><code>aws iam create-policy \
  --policy-name AllowExternalDNSUpdates \
  --policy-<span class="hljs-built_in">document</span> <span class="hljs-string">'{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["route53:ChangeResourceRecordSets"],"Resource":["arn:aws:route53:::hostedzone/*"]},{"Effect":"Allow","Action":["route53:ListHostedZones","route53:ListResourceRecordSets"],"Resource":["*"]}]}'</span>
</code></pre><ul>
<li>Use the policy ARN output above to create an IAM role bound to the ExternalDNS service account with a command that will look something like <code>eksctl create iamserviceaccount --cluster=your-cluster-name --name=external-dns --namespace=default --attach-policy-arn=arn:aws:iam::123456789:policy/AllowExternalDNSUpdates</code>.</li>
<li>We should now have a new role from the above that we can see in the <a target="_blank" href="https://console.aws.amazon.com/iam">IAM console</a> which will have a name of something like <code>eksctl-foo-addon-iamserviceaccount-Role1-abcdefg</code>. Click on the role from the list and at the top of the next screen make note of the "Role ARN" as something like <code>arn:aws:iam::123456789:role/eksctl-foo-addon-iamserviceaccount-Role1-abcdefg</code>.</li>
<li>Follow <a target="_blank" href="https://github.com/kubernetes-sigs/external-dns/blob/master/docs/tutorials/aws.md#set-up-a-hosted-zone">these steps</a> to create a "hosted zone" in Route 53.</li>
<li>You can confirm things in the <a target="_blank" href="https://console.aws.amazon.com/route53">Route 53 console</a>.</li>
<li>If your domain provider allows you to manage DNS settings, add the 4 name server records from the output of the command you ran to create a "hosted zone".</li>
<li>Deploy ExternalDNS by following <a target="_blank" href="https://github.com/kubernetes-sigs/external-dns/blob/master/docs/tutorials/aws.md#deploy-externaldns">the instructions</a>. Afterwards, you can tail the logs with <code>kubectl logs -f name-of-external-dns-pod</code>. You should see a line like this at the end: <code>time="2020-05-05T02:57:31Z" level=info msg="All records are already up to date"</code></li>
</ul>
<p>Easy, right?! Okay, maybe not... but at least you didn't have to figure all of that out alone ? There could be some gaps above, but hopefully it helps guide you through your process.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Although this post may have some grey areas, if it helps you establish dynamic DNS resolution as part of a highly available application, you've got something really special ?</p>
<p>Please add comments if I can help clear up anything or correct my terminology!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Brilliant Add-on For Static Sites That Will Make You Dance ]]>
                </title>
                <description>
                    <![CDATA[ By Jared Wolff This post is originally from www.jaredwolff.com Privacy. Performance. Brilliant looks. Can you have all three? (Of course!) Having a statically generated blog is great. Many folks use services like Disqus and Google Analytics to make t... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-setup-worry-free-blog-comments-in-20-simple-steps/</link>
                <guid isPermaLink="false">66d8505639c4dccc43d4d4a7</guid>
                
                    <category>
                        <![CDATA[ blog ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Docker compose ]]>
                    </category>
                
                    <category>
                        <![CDATA[ nginx ]]>
                    </category>
                
                    <category>
                        <![CDATA[ oauth ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Static Site Generators ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Mon, 08 Jul 2019 12:30:00 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2019/07/Copy-of-Static-Site-Docker-Recipes-2.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Jared Wolff</p>
<p><strong>This post is originally from <a target="_blank" href="https://www.jaredwolff.com/how-to-setup-worry-free-blog-comments-in-less-than-20-simple-steps/">www.jaredwolff.com</a></strong></p>
<p>Privacy.</p>
<p>Performance.</p>
<p>Brilliant looks.</p>
<p>Can you have all three?</p>
<p>(Of course!)</p>
<p>Having a statically generated blog is great. Many folks use services like Disqus and Google Analytics to make them even better. Not surprising if you were one of them!  Privacy concerns are are the forefront of everyone’s attention. So, rather than keeping the status quo, it’s time to do something about it!</p>
<p><strong>If you've been looking to protect your site visitor's privacy and improve performance this blog post is for you.</strong></p>
<p>In this article we'll be using DigitalOcean’s Docker droplet. It allows you to host several different applications/services on one (virtual) machine. By the end of it you'll know how to run your own comments server using Commento. Plus i’ll share a few tricks i’ve learned along the way to make it much easier for you.</p>
<p>Leeeets go!</p>
<h2 id="heading-reverse-proxy">Reverse Proxy</h2>
<p>One of the most important aspects of this setup is the reverse proxy. A reverse proxy acts like a router. Requests come in for a certain domain.  That request is then routed to the service associated with that domain.</p>
<p>Here’s a diagram from the Nginx Reverse Proxy + Let’s Encrypt Helper documentation. It'll help illustrate the idea.</p>
<p><img src="https://www.jaredwolff.com/how-to-setup-worry-free-blog-comments-in-less-than-20-simple-steps/images/webproxy-1f1c7540-4b86-4478-bb3e-f05043d671a5.jpg" alt="Nginx Reverse Proxy with Let's Encrypt" width="730" height="334" loading="lazy"></p>
<p>Another benefit is that there’s an extra layer of protection to the outside world. Your websites run in a private network and the only access is through the Nginx reverse proxy. Point your DNS to the server and Nginx handles all the magic.</p>
<p>Here's how to get it setup:</p>
<ol>
<li>Go ahead and set up your Digital Ocean Droplet. <a target="_blank" href="https://marketplace.digitalocean.com/apps/docker">All the info you need is right here</a>. The $5 version is more than sufficient.</li>
<li><p><a target="_blank" href="https://github.com/evertramos/docker-compose-letsencrypt-nginx-proxy-companion">Go here to clone the repository.</a> You can also run this in your terminal. Make sure you SSH into your Digital Ocean droplet first!</p>
<p>     git clone git@github.com:evertramos/docker-compose-letsencrypt-nginx-proxy-companion.git</p>
</li>
<li><p>Change directories to the cloned repository.</p>
</li>
<li>Copy <code>.env.sample</code> to <code>.env</code> and update the values inside. I had to change the <code>IP</code> value to the IP of my Digital Ocean Droplet. I left all the other ones alone.</li>
<li>Run <code>docker-compose up -d</code> to start everything. (you can run without the <code>-d</code> option to make sure everything starts ok. Or you can attach the log output using <code>docker container logs -f &lt;container name</code></li>
</ol>
<p>When pointing your sub-domains to this server, make sure you use an A record. Here's an example of mine:</p>
<p><img src="https://www.jaredwolff.com/how-to-setup-worry-free-blog-comments-in-less-than-20-simple-steps/images/Screen_Shot_2019-07-05_at_6-9c0432cd-4d40-4c89-88f3-24037d915eaf.52.32_PM.png" alt="NS1 A Record Configuration" width="730" height="581" loading="lazy"></p>
<p>Depending on your DNS provider, you'll have to figure out how to set an A record. That is beyond the purpose of this article though!</p>
<h2 id="heading-setting-up-commento-with-docker-compose">Setting Up Commento with Docker Compose</h2>
<p><img src="https://www.jaredwolff.com/how-to-setup-worry-free-blog-comments-in-less-than-20-simple-steps/images/Compose-1c868832-6819-43e2-8696-ab698a10dbee.jpg" alt="Commento Logo with Docker Logo" width="730" height="486" loading="lazy"></p>
<p>Here is the current docker compose file i'm using for Commento. It includes a few more environment variables for configuring Github, Gitlab and Google. It also includes the environment variables for setting the SMTP settings. These parameters are important. Otherwise you can't receive password reset or moderation emails!</p>
<p>    version: '3'</p>
<p>    services:
      commento:
        image: registry.gitlab.com/commento/commento
        container_name: commento
        restart: always
        environment:
          COMMENTO_ORIGIN: https://${COMMENTS_URL}
          COMMENTO_PORT: 8080
          COMMENTO_POSTGRES: postgres://postgres:postgres@postgres:5432/commento?sslmode=disable
          COMMENTO_SMTP_HOST: ${SMTP_HOST}
          COMMENTO_SMTP_PORT: ${SMTP_PORT}
          COMMENTO_SMTP_USERNAME: ${SMTP_USERNAME}
          COMMENTO_SMTP_PASSWORD: ${SMTP_PASSWORD}
          COMMENTO_SMTP_FROM_ADDRESS: ${SMTP_FROM_ADDRESS}
          COMMENTO_GITHUB_KEY: ${COMMENTO_GITHUB_KEY}
          COMMENTO_GITHUB_SECRET: ${COMMENTO_GITHUB_SECRET}
          COMMENTO_GITLAB_KEY: ${COMMENTO_GITLAB_KEY}
          COMMENTO_GITLAB_SECRET: ${COMMENTO_GITLAB_SECRET}
          COMMENTO_GOOGLE_KEY: ${COMMENTO_GOOGLE_KEY}
          COMMENTO_GOOGLE_SECRET: ${COMMENTO_GOOGLE_SECRET}
          COMMENTO_TWITTER_KEY: ${COMMENTO_TWITTER_KEY}
          COMMENTO_TWITTER_SECRET: ${COMMENTO_TWITTER_SECRET}
          VIRTUAL_HOST: ${COMMENTS_URL}
          VIRTUAL_PORT: 8080
          LETSENCRYPT_HOST: ${COMMENTS_URL}
          LETSENCRYPT_EMAIL: ${EMAIL}
        depends_on:</p>
<ul>
<li>postgres
networks:</li>
<li>db_network</li>
<li><p>webproxy</p>
<p>postgres:
image: postgres
container_name: postgres
environment:
POSTGRES_DB: commento
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
networks:</p>
</li>
<li>db_network
volumes:</li>
<li><p>postgres_data_volume:/var/lib/postgresql/data</p>
<p>networks:
db_network:
webproxy:
external: true</p>
<p>volumes:
postgres_data_volume:</p>
</li>
</ul>
<p>To set the environment variables, put them inside an <code>.env</code> file. Make sure the <code>.env</code> file is in the same directory as <code>docker-compose.yml</code>. When you run <code>docker-compose up</code> it will apply the variables set in the <code>.env</code> file. Nothing gets set if they're left blank.</p>
<p>Set the required <code>COMMENTS_URL</code> and <code>EMAIL</code> or you may run into problems. The best way to set these is by pacing them in the <code>.env</code> file. Here is an example:</p>
<p>    COMMENTS_URL=comments.your.url
    EMAIL=you@your.url</p>
<h2 id="heading-getting-oauth-key-amp-secret">Getting OAuth Key &amp; Secret</h2>
<p>Commento works with most popular OAuth providers. Thus visitors can leave comments without making an account.</p>
<p>The instructions are similar for each. I've outlined the steps for all of them below.</p>
<h3 id="heading-twitter">Twitter</h3>
<ol>
<li><p>Login to <a target="_blank" href="http://twitter.com">Twitter.com</a> and apply for a developer account: <a target="_blank" href="https://developer.twitter.com/en/application/use-case">https://developer.twitter.com/en/application/use-case</a></p>
<p> <img src="https://www.jaredwolff.com/how-to-setup-worry-free-blog-comments-in-less-than-20-simple-steps/images/Screen_Shot_2019-07-05_at_6-4171cdf7-6c2b-408b-bb64-57822ede91cb.26.08_PM.png" alt="Twitter API Access" width="730" height="581" loading="lazy"></p>
</li>
<li><p>Describe how you'll use the API. You can use what I wrote.</p>
<p> <img src="https://www.jaredwolff.com/how-to-setup-worry-free-blog-comments-in-less-than-20-simple-steps/images/Screen_Shot_2019-07-05_at_6-4c0aecf2-c020-4005-bd5f-81e3b4ac6b8f.28.43_PM.png" alt="How will you use the API?" width="730" height="581" loading="lazy"></p>
</li>
<li><p>Double check your entry and click <strong>Looks Good!</strong></p>
<p> <img src="https://www.jaredwolff.com/how-to-setup-worry-free-blog-comments-in-less-than-20-simple-steps/images/Screen_Shot_2019-07-05_at_6-ade63510-86d3-48a4-a121-221f6e14cd96.28.50_PM.png" alt="Is everything correct?" width="730" height="581" loading="lazy"></p>
</li>
<li><p>Agree to the terms of service.</p>
<p> <img src="https://www.jaredwolff.com/how-to-setup-worry-free-blog-comments-in-less-than-20-simple-steps/images/Screen_Shot_2019-07-05_at_6-2e8e3089-bd51-4d27-8573-6987aafc663e.28.59_PM.png" alt="Agree to Developer Agreement" width="730" height="581" loading="lazy"></p>
<p> <img src="https://www.jaredwolff.com/how-to-setup-worry-free-blog-comments-in-less-than-20-simple-steps/images/Screen_Shot_2019-07-05_at_6-145b1bfd-9fc7-4ea6-ba5f-032e59d7fe8d.41.47_PM.png" alt="You did it!" width="730" height="581" loading="lazy"></p>
</li>
<li><p>They'll tell you to check your email for a confirmation. Confirm your email and you should be able to create your first app!</p>
</li>
<li><p>Once approved to to <strong>Get started</strong> click <strong>Create an app</strong>.</p>
<p> <img src="https://www.jaredwolff.com/how-to-setup-worry-free-blog-comments-in-less-than-20-simple-steps/images/Screen_Shot_2019-07-05_at_6-640686b8-15c6-4af0-b9df-65ce15ae0fe7.29.22_PM.png" alt="Create an app" width="730" height="581" loading="lazy"></p>
</li>
<li><p>Next screen, again click <strong>Create an app</strong></p>
<p> <img src="https://www.jaredwolff.com/how-to-setup-worry-free-blog-comments-in-less-than-20-simple-steps/images/Screen_Shot_2019-07-05_at_6-de2b85d5-8bb7-428f-bfd1-2a23d0b7d4e0.29.26_PM.png" alt="Create an app" width="730" height="581" loading="lazy"></p>
</li>
<li><p>Enter all the appropriate details. For the callback URL, use <a target="_blank" href="https://comments.jaredwolff.com/api/oauth/google/callback"><code>https://&lt;your URL&gt;/api/oauth/github/callback</code></a> where <a target="_blank" href="https://comments.jaredwolff.com/api/oauth/google/callback"><code>&lt;your URL&gt;</code></a> is your Commento subdomain.</p>
<p> <img src="https://www.jaredwolff.com/how-to-setup-worry-free-blog-comments-in-less-than-20-simple-steps/images/Screen_Shot_2019-07-05_at_6-91acb343-9dee-4917-be77-9704fe439722.32.44_PM.png" alt="App details" width="730" height="581" loading="lazy"></p>
</li>
<li><p>Finally, once you're done filling out the information to go the <strong>Keys and Token</strong>s area. Save both the key and token. Enter them into the <code>.env</code> file. You can use <code>COMMENTO_TWITTER_KEY</code> and <code>COMMENTO_TWITTER_SECRET</code></p>
<p> <img src="https://www.jaredwolff.com/how-to-setup-worry-free-blog-comments-in-less-than-20-simple-steps/images/Screen_Shot_2019-07-05_at_6-b910e9ff-dc34-45e8-94df-affb06702617.33.07_PM.png" alt="Get oauth key and secret" width="730" height="581" loading="lazy"></p>
</li>
</ol>
<h3 id="heading-gitlab">Gitlab</h3>
<ol>
<li>Login to <a target="_blank" href="http://gitlab.com">Gitlab.com</a> and go to to top right and click <strong>Settings</strong></li>
<li><p>Then click on <strong>Applications</strong></p>
<p> <img src="https://www.jaredwolff.com/how-to-setup-worry-free-blog-comments-in-less-than-20-simple-steps/images/Screen_Shot_2019-07-05_at_12-c6da9d02-2052-4fa4-89de-d5212b8f49ca.56.47_PM.png" alt="Gitlab profile" width="730" height="445" loading="lazy"></p>
</li>
<li><p>Enter a name for your app. I put <strong>Commento</strong>.</p>
</li>
<li>Set the Redirect URI to <a target="_blank" href="https://comments.jaredwolff.com/api/oauth/google/callback"><code>https://&lt;your URL&gt;/api/oauth/gitlab/callback</code></a></li>
<li><p>Select the <strong>read_user</strong> scope.</p>
<p> <img src="https://www.jaredwolff.com/how-to-setup-worry-free-blog-comments-in-less-than-20-simple-steps/images/Screen_Shot_2019-07-05_at_12-e616c338-6144-4704-93c6-914db6fad5f6.59.15_PM.png" alt="Gitlab add application" width="730" height="500" loading="lazy"></p>
</li>
<li><p>Click the green <strong>Save Application</strong> button</p>
</li>
<li><p>Copy the <strong>Application ID</strong> and <strong>Secret</strong> and place them in your <code>.env</code> file using <code>COMMENTO_GITLAB_KEY</code> and <code>COMMENTO_GITLAB_SECRET</code></p>
<p> <img src="https://www.jaredwolff.com/how-to-setup-worry-free-blog-comments-in-less-than-20-simple-steps/images/Screen_Shot_2019-07-05_at_1-a4f4ab4a-9fd6-423f-821c-6ff2f174e589.04.10_PM.png" alt="Application key and secret" width="730" height="689" loading="lazy"></p>
</li>
</ol>
<h3 id="heading-github">Github</h3>
<ol>
<li>To get your OAuth key and secret, you'll need to go to this URL: <a target="_blank" href="https://github.com/settings/developers">https://github.com/settings/developers</a></li>
<li><p>Once there, click on <strong>New OAuth App</strong></p>
<p> <img src="https://www.jaredwolff.com/how-to-setup-worry-free-blog-comments-in-less-than-20-simple-steps/images/Screen_Shot_2019-07-04_at_9-18bf8f23-916f-476b-8c25-3377de931fe3.15.33_AM.png" alt="Add OAuth application" width="730" height="562" loading="lazy"></p>
</li>
<li><p>Enter your details. For the callback URL, use <a target="_blank" href="https://comments.jaredwolff.com/api/oauth/google/callback"><code>https://&lt;your URL&gt;/api/oauth/github/callback</code></a> where <a target="_blank" href="https://comments.jaredwolff.com/api/oauth/google/callback"><code>&lt;your URL&gt;</code></a> is your Commento subdomain.</p>
<p> <img src="https://www.jaredwolff.com/how-to-setup-worry-free-blog-comments-in-less-than-20-simple-steps/images/Screen_Shot_2019-07-04_at_9-6e616334-7123-4de4-a4fd-f2fe319b1971.28.24_AM.png" alt="Register new OAuth application" width="730" height="585" loading="lazy"></p>
<p> <em>Note: Make sure you include <code>https</code> in your URLs.</em></p>
</li>
<li><p>Grab the <strong>Client ID</strong> and <strong>Client secret</strong> and put that into your <code>.env</code> file using <code>COMMENTO_GITHUB_KEY</code> and <code>COMMENTO_GITHUB_SECRET</code></p>
<p> <img src="https://www.jaredwolff.com/how-to-setup-worry-free-blog-comments-in-less-than-20-simple-steps/images/Screen_Shot_2019-07-04_at_9-7505a3ef-386a-4b75-a7dc-1dd3e22d0baf.29.28_AM.png" alt="Application created successfully" width="730" height="585" loading="lazy"></p>
</li>
</ol>
<h3 id="heading-google">Google</h3>
<p>Setting up Google is just about as tedious to set up as Twitter. Despite how scary I just made it out to be, it's completely doable. Here are the steps.</p>
<ol>
<li>Go to this URL: <a target="_blank" href="https://console.developers.google.com/cloud-resource-manager?previousPage=%2Fapi">Google Developer Console</a></li>
<li><p>Create a new project</p>
<p> <img src="https://www.jaredwolff.com/how-to-setup-worry-free-blog-comments-in-less-than-20-simple-steps/images/Screen_Shot_2019-07-04_at_8-f3793926-cc54-4345-b81c-5ec0f4631a35.42.48_AM.png" alt="Create a new project" width="730" height="588" loading="lazy"></p>
</li>
<li><p>Click the <strong>GoogleAPIs logo</strong> in the top left corner to go back once you have a project. (Make sure the dropdown next to the <strong>GoogleAPIs logo</strong> is the same as your new project!)</p>
</li>
<li>Then, click <strong>Credentials</strong> on the left side.</li>
<li><p>Update the <strong>Application Name</strong> and <strong>Authorized Domains</strong> in the <strong>OAuth consent screen</strong></p>
<p> <img src="https://www.jaredwolff.com/how-to-setup-worry-free-blog-comments-in-less-than-20-simple-steps/images/Screen_Shot_2019-07-04_at_8-d839a5c9-3368-4f18-b674-73b6e4e7c17c.47.15_AM.png" alt="Setup application" width="730" height="499" loading="lazy"></p>
</li>
<li><p>Click <strong>Create credentials</strong> then <strong>OAuth client ID</strong></p>
<p> <img src="https://www.jaredwolff.com/how-to-setup-worry-free-blog-comments-in-less-than-20-simple-steps/images/Screen_Shot_2019-07-04_at_8-201545f9-4d47-4e0c-ae9a-b40efdc35a4b.44.36_AM.png" alt="Setup credentials" width="730" height="545" loading="lazy"></p>
</li>
<li><p>On the <strong>Create OAuth client ID</strong> enter the subdomain associated with Commento to <strong>Authorized Javascript origins.</strong> Then, enter the full callback URL. For example <a target="_blank" href="https://comments.jaredwolff.com/api/oauth/google/callback"><code>https://comments.jaredwolff.com/api/oauth/google/callback</code></a>. Make it yours by replacing <code>comments.jaredwolff.com</code> with your URL.</p>
<p> <img src="https://www.jaredwolff.com/how-to-setup-worry-free-blog-comments-in-less-than-20-simple-steps/images/Screen_Shot_2019-07-04_at_8-fdba3491-d562-41f3-acff-2857ea816cec.52.15_AM.png" alt="Create OAuth Client ID" width="730" height="706" loading="lazy"></p>
<p> Once entered, click the <strong>create</strong> button.</p>
</li>
<li><p>Grab the <strong>client ID</strong> and <strong>client secret</strong></p>
<p> <img src="https://www.jaredwolff.com/how-to-setup-worry-free-blog-comments-in-less-than-20-simple-steps/images/Screen_Shot_2019-07-04_at_8-0c3f2895-0cb9-4b3a-a154-a3d80fd9716a.57.40_AM.png" alt="OAuth Credentials" width="730" height="706" loading="lazy"></p>
</li>
<li><p>Update your <code>.env</code> file using <code>COMMENTO_GOOGLE_KEY</code> and <code>COMMENTO_GOOGLE_SECRET</code></p>
</li>
</ol>
<h2 id="heading-install-your-application">Install your application</h2>
<p>You've entered your OAuth Credentials email, domain and SMTP credentials. It's time to wrap this show up!</p>
<ol>
<li>Once you're done editing your <code>.env</code> file. Run <code>docker-compose up</code> (For files not named <code>docker-compose.yml</code>, use the <code>-f</code> flag. Example: <code>docker-compose -f commento.yml up</code></li>
<li>Watch the output for errors. If it looks good you may want to kill it (<strong>CTRL+C</strong>) and run with the <code>-d</code> flag</li>
<li><p>On first start, Commento will prompt you with a login screen.</p>
<p> <img src="https://www.jaredwolff.com/how-to-setup-worry-free-blog-comments-in-less-than-20-simple-steps/images/Screen_Shot_2019-07-05_at_12-d5a1ca53-93b3-49c5-a3a7-e8b728259e2d.11.29_PM.png" alt="Commento Login" width="730" height="545" loading="lazy"></p>
</li>
<li><p>Create a new account by clicking <strong>Don't have an account yet? Sign up.</strong></p>
</li>
<li>Enter your information and click <strong>Sign Up</strong></li>
<li><p>Check your email and click the included link:</p>
<p> <img src="https://www.jaredwolff.com/how-to-setup-worry-free-blog-comments-in-less-than-20-simple-steps/images/Screen_Shot_2019-07-05_at_12-e263aa4f-201b-42ac-986c-b28c5f003f38.12.48_PM.png" alt="Validation email with link" width="730" height="733" loading="lazy"></p>
</li>
<li><p>Log in with your freshly made account.</p>
</li>
<li><p>Then, click <strong>Add a New Domain.</strong></p>
<p> <img src="https://www.jaredwolff.com/how-to-setup-worry-free-blog-comments-in-less-than-20-simple-steps/images/Screen_Shot_2019-07-05_at_12-46acfe9c-f3f4-4d3e-b8fb-97fbff643a86.10.47_PM.png" alt="Add new domain" width="730" height="598" loading="lazy"></p>
</li>
<li><p>Once created go to <strong>Installation Guide.</strong>  Copy the snippet and place it where ever you want your comments to live. In my case, I put the snippet in an area just after my <code>&lt;article&gt;</code> tag.</p>
<p> <img src="https://www.jaredwolff.com/how-to-setup-worry-free-blog-comments-in-less-than-20-simple-steps/images/Screen_Shot_2019-07-05_at_12-f78f36c5-f3f7-45ec-971d-9bf0bf7b7d1f.36.35_PM.png" alt="Code snippet" width="730" height="589" loading="lazy"></p>
</li>
<li><p>Re-compile your site and check for success!</p>
<p><img src="https://www.jaredwolff.com/how-to-setup-worry-free-blog-comments-in-less-than-20-simple-steps/images/Screen_Shot_2019-07-05_at_12-8f7ffbdc-c49f-49bc-95bb-1f53a926f361.30.27_PM.png" alt="Blog comment section with checkmarks" width="730" height="589" loading="lazy"></p>
<p>Checkmark! Finally, I recommend you try logging in with each individual OAuth configuration. That way you know it working for your website visitors. ?</p>
</li>
</ol>
<h2 id="heading-alternatives">Alternatives</h2>
<p>I spent a good chunk playing around with some of the alternatives. This is by no means a definitive guide on what will work best for your site. Here are some of the top ones as of this writing:</p>
<p><a target="_blank" href="https://utteranc.es/#configuration">https://utteranc.es/#configuration</a></p>
<p><a target="_blank" href="https://github.com/netlify/gotell">https://github.com/netlify/gotell</a></p>
<p><a target="_blank" href="https://github.com/eduardoboucas/staticman">https://github.com/eduardoboucas/staticman</a></p>
<p><a target="_blank" href="https://posativ.org/isso/">https://posativ.org/isso/</a></p>
<p><a target="_blank" href="https://www.remarkbox.com/">https://www.remarkbox.com</a></p>
<p><a target="_blank" href="https://www.vis4.net/blog/2017/10/hello-schnack/">https://www.vis4.net/blog/2017/10/hello-schnack/</a></p>
<p><a target="_blank" href="https://github.com/gka/schnack">https://github.com/gka/schnack</a></p>
<p>There's also a huge thread over at the Hugo blog which has a ton more links and resources as well:</p>
<p><a target="_blank" href="https://discourse.gohugo.io/t/alternative-to-disqus-needed-more-than-ever/5516">https://discourse.gohugo.io/t/alternative-to-disqus-needed-more-than-ever/5516</a></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Congrats! You are now hosting your own comments server! ?</p>
<p>In this article you've learned how to harness the power of Docker and a Nginx Reverse Proxy. As an added bonus, you know how to  set up OAuth credentials! That way future setup will be easy peasy.</p>
<p>By the way, this is only the tip of the iceberg. You can set up the same server for analytics, data collection and more. <a target="_blank" href="https://www.jaredwolff.com/files/host-your-comments/">All the example code including code for other applications can be found here.</a></p>
<p>Finally, if you're looking pay for Commento head to <a target="_blank" href="http://www.commento.io">www.commento.io</a> and sign up for the service. You'll be supporting awesome open source software!</p>
<p>If you have comments and questions let's hear em'. Start the conversation down below. ???</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Docker 101: Fundamentals and Practice ]]>
                </title>
                <description>
                    <![CDATA[ By Guilherme Pejon If you're tired of hearing your coworkers praise Docker and its benefits at every chance they get, or you're tired of nodding your head and walking away every time you find yourself in one of these conversations, you've come to the... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/docker-101-fundamentals-and-practice-edb047b71a51/</link>
                <guid isPermaLink="false">66c34965c8f6b2d81069b345</guid>
                
                    <category>
                        <![CDATA[ Docker ]]>
                    </category>
                
                    <category>
                        <![CDATA[ nginx ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Productivity ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Python ]]>
                    </category>
                
                    <category>
                        <![CDATA[ tech  ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Sun, 14 Apr 2019 22:54:17 +0000</pubDate>
                <media:content url="https://cdn-media-1.freecodecamp.org/images/0*mgaoUlIJzr502lhY.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Guilherme Pejon</p>
<p>If you're tired of hearing your coworkers praise Docker and its benefits at every chance they get, or you're tired of nodding your head and walking away every time you find yourself in one of these conversations, you've come to the right place.</p>
<p>Also, if you are looking for a new excuse to wander off without getting fired, keep reading and you'll thank me later.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/GXyh0RbblBFolxVst-ZN6DWd3CnUk5aREezM" alt="Image" width="413" height="360" loading="lazy">
_Source: [developermemes](http://www.developermemes.com" rel="noopener" target="<em>blank" title=")</em></p>
<h3 id="heading-docker">Docker</h3>
<p>Here's Docker's definition, according to <a target="_blank" href="https://en.wikipedia.org/wiki/Docker_(software)">Wikipedia</a>:</p>
<blockquote>
<p>Docker is a computer program that performs operating-system-level virtualization.</p>
</blockquote>
<p>Pretty simple, right? Well, not exactly. Alright, here's my definition of what docker is:</p>
<blockquote>
<p>Docker is a platform for creating and running <strong>containers</strong> from <strong>images</strong>.</p>
</blockquote>
<p>Still lost? No worries, that's because you probably don't know what <strong>containers</strong> or <strong>images</strong> are.</p>
<p><strong>Images</strong> are single files containing all the dependencies and configurations required to run a program, while <strong>containers</strong> are the instances of those images. Let's go ahead and see an example of that in practice to make things clearer.</p>
<blockquote>
<p><strong>Important note:</strong> Before you continue, make sure you <a target="_blank" href="https://docs.docker.com/install/">install docker</a> using the recommended steps for your operating system.</p>
</blockquote>
<h3 id="heading-part-1-hello-world-from-a-python-image">Part 1. "Hello, World!" from a Python image</h3>
<p>Let's say you don't have Python installed in your machine — or at least not the latest version - and you need python to print "Hello, World!" in your terminal. What do you do? You use docker!</p>
<p>Go ahead and run the following command:</p>
<pre><code>docker run --rm -it python:<span class="hljs-number">3</span> python
</code></pre><p>Don't worry, I'll explain that command in a second, but right now you are probably seeing something like this:</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/i7DalGXQXEQYdUQS2FMMsn0x62Kz1KSq8Igx" alt="Image" width="800" height="380" loading="lazy">
<em>It might take a few moments for this command to run for the first time</em></p>
<p>That means we are currently inside a <strong>docker container</strong> created from a python 3 <strong>docker image,</strong> running the <code>python</code> command. To finish off the example, type <code>print("Hello, World!")</code> and watch as the magic happens.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/A-pKIHg3d-pdx9vDt50v3bmW6z0JTTde5X-P" alt="Image" width="374" height="94" loading="lazy">
<em>A "Hello, World!". Much wow!</em></p>
<p>Alright, you did it, but before you start patting yourself on the back, let's take a step back and understand how that worked.</p>
<h4 id="heading-breaking-it-down"><strong>Breaking it down</strong></h4>
<p>Let's start from the beginning. The <code>docker run</code> command is docker's standard tool to help you start and run your containers.</p>
<p>The <code>--rm</code> flag is there to tell the Docker Daemon to clean up the container and remove the file system after the container exits. This helps you save disk space after running short-lived containers like this one, that we only started to print "Hello, World!".</p>
<p>The <code>-t (or --tty)</code> flag tells Docker to allocate a virtual terminal session within the container. This is commonly used with the <code>-i (or --interactive)</code> option, which keeps STDIN open even if running in detached mode (more about that later).</p>
<blockquote>
<p><strong>Note:</strong> Don't worry too much about these definitions right now. Just know that you will use the<code>-it</code> flag anytime you want to type some commands on your container.</p>
</blockquote>
<p>Lastly, <code>python:3</code> is the base image we used for this container. Right now, this image comes with python version 3.7.3 installed, among other things. Now, you might be wondering where did this image came from, and what's inside of it. You can find the answers to both of these questions right <a target="_blank" href="https://hub.docker.com/_/python/">here</a>, along with all the other python images we could have used for this example.</p>
<p>Last but not least, <code>python</code> was the command we told Docker to execute inside our <code>python:3</code> image, which started a python shell and allowed our <code>print("Hello, World!")</code> call to work.</p>
<h4 id="heading-one-more-thing">One more thing</h4>
<p>To exit python and terminate our container, you can use CTRL/CMD + D or <code>exit()</code>. Go ahead and do that right now. After that, try to execute our <code>docker run</code> command again and you'll see something a little bit different, and a lot faster.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/PjuutqjPnUNWh3q5mgQjJ0hs9qEqfPgJAlG1" alt="Image" width="800" height="138" loading="lazy">
<em>Much faster. Wow!</em></p>
<p>That's because we already downloaded the <code>python:3</code> image, so our container starts a lot faster now.</p>
<h3 id="heading-part-2-automated-hello-world-from-a-python-image">Part 2. Automated "Hello World!" from a Python image</h3>
<p>What's better than writing "Hello, World!" in your terminal once? You got it, writing it twice!</p>
<p>Since we cannot wait to see "Hello, World!" printed in our terminal again, and we don't want to go through the hustle of opening up python and typing <code>print</code> again, let's go ahead and automate that process a little bit. Start by creating a <code>hello.py</code> file anywhere you'd like.</p>
<pre><code># hello.py
</code></pre><pre><code>print(<span class="hljs-string">"Hello, World!"</span>)
</code></pre><p>Next, go ahead and run the following command from that same folder.</p>
<pre><code>docker run --rm -it -v $(pwd):<span class="hljs-regexp">/src python:3 python /</span>src/hello.py
</code></pre><p>This is the result we are looking for:</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/NEXiMrDxjadvG2DEDuGRSXg05HsDuqA5QAju" alt="Image" width="800" height="128" loading="lazy">
<em>Great! YAHW (Yet Another "Hello World!")</em></p>
<blockquote>
<p><strong>Note:</strong> I used <code>ls</code> before the command to show you that I was in the same folder that I created the <code>hello.py</code> file in.</p>
</blockquote>
<p>As we did earlier, let's take a step back and understand how that worked.</p>
<h4 id="heading-breaking-it-down-1">Breaking it down</h4>
<p>We are pretty much running the same command we ran in the last section, apart from two things.</p>
<p>The <code>-v $(pwd):/src</code> option tells the Docker Daemon to start up a <strong>volume i</strong>n our container<strong>.</strong> Volumes are the best way to persist data in Docker. In this example, we are telling Docker that we want the current directory - retrieved from <code>$(pwd)</code> - to be added to our container in the folder <code>/src</code>.</p>
<blockquote>
<p><strong>Note:</strong> You can use any other name or folder that you want, not only <code>/src</code></p>
</blockquote>
<p>If you want to check that <code>/src/hello.py</code> actually exists inside our container, you can change the end of our command from <code>python hello.py</code> to <code>bash</code>. This will open an interactive shell inside our container, and you can use it just like you would expect.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/IbjkecLbC0HhtIFcvHz7IYEEubn05ZU1Gnkt" alt="Image" width="784" height="220" loading="lazy">
<em>Isn't that crazy?</em></p>
<blockquote>
<p><strong>Note:</strong> We can only use <code>bash</code> here because it comes pre-installed in the <code>python:3</code> image. Some images are so simple that they don't even have <code>bash</code>. That doesn't mean you can't use it, but you'll have to install it yourself if you want it.</p>
</blockquote>
<p>The last bit of our command is the <code>python /src/hello.py</code> instruction. By running it, we are telling our container to look inside its <code>/src</code> folder and execute the <code>hello.py</code> file using <code>python</code>.</p>
<p>Maybe you can already see the wonders you can do with this power, but I'll highlight it for you anyway. Using what we just learned, we can pretty much run <strong>any code</strong> from <strong>any language</strong> inside <strong>any computer</strong> without having to install <strong>any dependencies</strong> at the host machine - except for Docker, of course. That's a lot of <strong>bold text</strong> for one sentence, so make sure you read that twice!</p>
<h3 id="heading-part-3-easiest-hello-world-possible-from-a-python-image-using-dockerfile">Part 3. Easiest "Hello, World!" possible from a Python image using Dockerfile</h3>
<p>Are you tired of saying hello to our beautiful planet, yet? That's a shame, cause we are gonna do it again!</p>
<p>The last command we learned was a little bit verbose, and I can already see myself getting tired of typing all of that code every time I wanna say "Hello, World!" Let's automate things a little bit further now. Create a file named <code>Dockerfile</code> and add the following content to it:</p>
<pre><code># Dockerfile
</code></pre><pre><code>FROM python:<span class="hljs-number">3</span>
</code></pre><pre><code>WORKDIR /src/app
</code></pre><pre><code>COPY . .
</code></pre><pre><code>CMD [ <span class="hljs-string">"python"</span>, <span class="hljs-string">"./hello.py"</span> ]
</code></pre><p>Now run this command in the same folder you created the <code>Dockerfile</code>:</p>
<pre><code>docker build -t hello .
</code></pre><p>All that's left to do now is to go crazy using this code:</p>
<pre><code>docker run hello
</code></pre><p><img src="https://cdn-media-1.freecodecamp.org/images/xkJM8U1zy7RgVHoFgMXr1ZbRxRmynsDIz1Jr" alt="Image" width="274" height="320" loading="lazy">
<em>Note that you don’t even need to be in the same folder anymore</em></p>
<p>You already know how it is. Let's take a moment to understand how a Dockerfile works now.</p>
<h4 id="heading-breaking-it-down-2">Breaking it down</h4>
<p>Starting with our Dockerfile, the first line <code>FROM python:3</code> is telling Docker to start everything with the base image we are already familiar with, <code>python:3</code>.</p>
<p>The second line, <code>WORKDIR /src/app</code>, sets the working directory inside our container. This is for some instructions that we'll execute later, like <code>CMD</code> or <code>COPY</code>. You can see the rest of the supported instructions for <code>WORKDIR</code> right <a target="_blank" href="https://docs.docker.com/engine/reference/builder/#workdir">here</a>.</p>
<p>The third line, <code>COPY . .</code> is basically telling Docker to copy everything from our current folder (first <code>.</code>), and paste it on <code>/src/app</code> (second <code>.</code>). The paste location was set with the <code>WORKDIR</code> command right above it.</p>
<blockquote>
<p><strong>Note:</strong> We could achieve the same results by removing the <code>WORKDIR</code> instruction and replacing the <code>COPY . .</code> instruction with <code>COPY . /src/app</code>. In that case, we would also need to change the last instruction, <code>CMD ["python", "./hello.py"]</code> to <code>CMD ["python", "/src/app/hello.py"]</code>.</p>
</blockquote>
<p>Finally, the last line <code>CMD ["python", "./hello.py"]</code> is providing the default command for our container. It's essentially saying that every time we <code>run</code> a container from this configuration, it should run <code>python ./hello.py</code>. Keep in mind that we are implicitly running <code>/src/app/hello.py</code> instead of only <code>hello.py</code>, since that's what where we pointed our <code>WORKDIR</code> to.</p>
<blockquote>
<p><strong>Note:</strong> The <code>CMD</code> command can be overwritten at runtime. For instance, if you want to run <code>bash</code> instead, you would do <code>docker run hello bash</code> after building the container.</p>
</blockquote>
<p>With our Dockerfile finished, we go ahead and start our <code>build</code> process. The <code>docker build -t hello .</code> command reads all the configuration we added to our Dockerfile and creates a <strong>docker image</strong> from it. That's right, just like the <code>python:3</code> image we've been using for this entire article. The <code>.</code> at the end tells Docker that we want to run a Dockerfile at our current location, and the <code>-t hello</code> option gives this image the name <code>hello</code>, so we can easily reference it at runtime.</p>
<p>After all of that, all we need to do is run the usual <code>docker run</code> instruction, but this time with the <code>hello</code> image name at the end of the line. That will start a container from the image we recently built and finally print the good ol' "Hello, World!" in our terminal.</p>
<h4 id="heading-extending-our-base-image">Extending our base image</h4>
<p>What do we do if we need some dependency to run our code that does not come pre-installed with our base image? To solve that problem, docker has the <code>RUN</code> <a target="_blank" href="https://docs.docker.com/engine/reference/builder/#run">instruction</a>.</p>
<p>Following our python example, if we needed the <code>numpy</code> library to run our code, we could add the <code>RUN</code> instruction right after our <code>FROM</code> command.</p>
<pre><code># Dockerfile
</code></pre><pre><code>FROM python:<span class="hljs-number">3</span>
</code></pre><pre><code># NEW LINERUN pip3 install numpy
</code></pre><pre><code>WORKDIR /src/app
</code></pre><pre><code>COPY . .
</code></pre><pre><code>CMD [ <span class="hljs-string">"python"</span>, <span class="hljs-string">"./hello.py"</span> ]
</code></pre><p>The <code>RUN</code> instruction basically gives a command to be executed by the container's terminal. That way, since our base image already comes with <code>pip3</code> installed, we can use <code>pip3 install numpy</code>.</p>
<blockquote>
<p><strong>Note:</strong> For a real python app, you would probably add all the dependencies you need to a <code>requirements.txt</code> file, copy it over to the container, and then update the <code>RUN</code> instruction to <code>RUN pip3 install -r requirements.txt</code>.</p>
</blockquote>
<h3 id="heading-part-4-hello-world-from-a-nginx-image-using-a-long-lived-detached-container">Part 4. "Hello, World!" from a Nginx image using a long-lived detached container</h3>
<p>I know you are probably tired of hearing me say it, but I have one more "Hello" to say before I go. Let's go ahead and use our newly acquired docker power to create a simple long-lived container, instead of these short-lived ones we've been using so far.</p>
<p>Create an <code>index.html</code> file in a new folder with the following content.</p>
<pre><code># index.html
</code></pre><pre><code>&lt;h1&gt;Hello, World!&lt;/h1&gt;
</code></pre><p>Now, let's create a new Dockerfile in the same folder.</p>
<pre><code># Dockerfile
</code></pre><pre><code>FROM nginx:alpine
</code></pre><pre><code>WORKDIR /usr/share/nginx/html
</code></pre><pre><code>COPY . .
</code></pre><p>Build the image and give it the name <code>simple_nginx</code>, like we previously did.</p>
<pre><code>docker build -t simple_nginx .
</code></pre><p>Lastly, let's run our newly created image with the following command:</p>
<pre><code>docker run --rm -d -p <span class="hljs-number">8080</span>:<span class="hljs-number">80</span> simple_nginx
</code></pre><p>You might be thinking that nothing happened because you are back to your terminal, but let's take a closer look with the <code>docker ps</code> command.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/dc8GKlsLJ6mKsn7r-6Z6LQQIlKoy9kjUhvxO" alt="Image" width="800" height="99" loading="lazy">
<em>I had to crop the output, but you’ll see a few other columns there</em></p>
<p>The <code>docker ps</code> command shows all the running containers in your machine. As you can see in the image above, I have a container named <code>simple_nginx</code> running in my machine right now. Let's open up a web browser and see if <code>nginx</code> is doing its job by accessing <code>localhost:8080</code>.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/BuDs3TFSrsnUeN3VggrmWNi3GjKTdL5WnQTx" alt="Image" width="634" height="514" loading="lazy">
<em>Hurray! (this is the last time, I promise)</em></p>
<p>Everything seems to be working as expected, and we are serving a static page through the <code>nginx</code> running inside our container. Let's take a moment to understand how we accomplished that.</p>
<h4 id="heading-breaking-it-down-3">Breaking it down</h4>
<p>I'm going to skip the Dockerfile explanation because we already learned those commands in the last section. The only "new" thing in that configuration is the <code>nginx:alpine</code> image, which you can read more about it <a target="_blank" href="https://hub.docker.com/_/nginx">here</a>.</p>
<p>Apart from what is new, this configuration works because <code>nginx</code> uses the <code>usr/share/nginx/html</code> folder to search for an <code>index.html</code> file and start serving it, so since we named our file <code>index.html</code> and configured the <code>WORKDIR</code> to be <code>usr/share/nginx/html</code>, this setup will work right out of the box.</p>
<p>The <code>build</code> command is exactly like the one we used in the last section as well, we are only using the Dockerfile configuration to build an image with a certain name.</p>
<p>Now for the fun part, the <code>docker run --rm -d -p 8080:80 simple_nginx</code> instruction. Here we have two new flags. The first one is the detached (<code>-d</code>) flag, which means that we want to run this container in the background, and that's why we are back at our terminal right after using the <code>docker run</code> command, even though our container is still running.</p>
<p>The second new flag is the <code>-p 8080:80</code> option. As you might have guessed, this is the <code>port</code> flag, and it's basically mapping the port <code>8080</code> from our local machine to the port <code>80</code> inside our container. You could have used any other port instead of <code>8080</code>, but you cannot change the port <code>80</code> without adding an additional setting to the <code>nginx</code> image, since <code>80</code> is the standard port the <code>nginx</code> image exposes.</p>
<blockquote>
<p><strong>Note:</strong> If you want to stop a detached container like this one, you can use the <code>docker ps</code> command to get the <strong>container's name</strong> (not image), and then use the <code>docker stop</code> instruction with the desired container's name at the end of the line.</p>
</blockquote>
<h3 id="heading-part-5-the-end">Part 5. The end</h3>
<p>That's it! If you are still reading this, you have all the basics to start using Docker today on your personal projects or daily work.</p>
<p>Let me know what you thought about this article in the comments, and I'll make sure to write a follow-up article covering more advanced topics like <code>docker-compose</code> somewhere in the near future.</p>
<p>If you have any questions, please let me know.</p>
<p>Cheers!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to set up an easy and secure reverse proxy with Docker, Nginx & Letsencrypt ]]>
                </title>
                <description>
                    <![CDATA[ By Kasper Siig Introduction Ever tried setting up some sort of server at home? Where you have to open a new port for every service? And have to remember what port goes to which service, and what your home ip is? This is definitely something that work... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/docker-nginx-letsencrypt-easy-secure-reverse-proxy-40165ba3aee2/</link>
                <guid isPermaLink="false">66c349772daea10bbc11930e</guid>
                
                    <category>
                        <![CDATA[ Docker ]]>
                    </category>
                
                    <category>
                        <![CDATA[ nginx ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Security ]]>
                    </category>
                
                    <category>
                        <![CDATA[ tech  ]]>
                    </category>
                
                    <category>
                        <![CDATA[ technology ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Thu, 11 Apr 2019 04:50:41 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2020/04/john-salvino-bqGBbLq_yfc-unsplash.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Kasper Siig</p>
<h2 id="heading-introduction">Introduction</h2>
<p>Ever tried setting up some sort of server at home? Where you have to open a new port for every service? And have to remember what port goes to which service, and what your home ip is? This is definitely something that works, and people have been doing it for the longest time.</p>
<p>However, wouldn’t it be nice to type <em>plex.example.com</em>, and have instant access to your media server? This is exactly what a reverse proxy will do for you, and combining it with Docker, it’s easier than ever.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<h3 id="heading-docker-amp-docker-compose">Docker &amp; Docker-Compose</h3>
<p>You should have Docker version 17.12.0+, and Compose version 1.21.0+.</p>
<h3 id="heading-domain">Domain</h3>
<p>You should have a domain set up, and have an SSL Certificate associated with it. If you don’t have one, then <a target="_blank" href="https://medium.com/devopslinks/docker-letsencrypt-dns-validation-75ba8c08a0d">follow my guide here</a> on how to get a free one with LetsEncrypt.</p>
<h2 id="heading-what-this-article-will-cover">What This Article Will Cover</h2>
<p>I’m a firm believer in understanding what you are doing. There was a time where I would follow guides, and have no clue on how to troubleshoot failures. If that’s how you want to do it, <a target="_blank" href="https://medium.freecodecamp.org/docker-compose-nginx-and-letsencrypt-setting-up-website-to-do-all-the-things-for-that-https-7cb0bf774b7e">here’s a great tutorial</a>, which covers how to set it up. While my articles are lengthy, you should end up with an understanding of how it all works.</p>
<p>What you will learn here, is what a reverse proxy is, how to set it up, and how you can secure it. I do my best to divide the subject into sections, divided by headers, so feel free to jump over a section, if you feel like it. I recommend reading the entire article one time first, before starting to set it up.</p>
<h2 id="heading-what-is-a-reverse-proxy">What is a Reverse Proxy?</h2>
<h3 id="heading-regular-proxy">Regular Proxy</h3>
<p>Let’s start with the concept of a regular proxy. While this is a term that’s very prevalent in the tech community, it is not the only place it’s used. A proxy means that information is going through a third party, before getting to the location.</p>
<p>Say that you don’t want a service to know your IP, you can use a proxy. A proxy is a server that has been set up specifically for this purpose. If the proxy server you are using is located in, for example, Amsterdam, the IP that will be shown to the outside world is the IP from the server in Amsterdam. The only ones who will know your IP are the ones in control of the proxy server.</p>
<h3 id="heading-reverse-proxy">Reverse Proxy</h3>
<p>To break it into simple terms, a proxy will add a layer of masking. It’s the same concept in a reverse proxy, except instead of masking outgoing connections (you accessing a webserver), it’s the incoming connections (people accessing your webserver) that will be masked. You simply provide a URL like <em>example.com</em>, and whenever people access that URL, your reverse proxy will take care of where that request goes.</p>
<p>Let’s say you have two servers set up on your internal network. Server1 is on <em>192.168.1.10</em>, and Server2 is on <em>192.168.1.20.</em> Right now your reverse proxy is sending requests coming from <em>example.com</em> to Server1. One day you have some updates to the webpage. Instead of taking the website down for maintenance, you just make the new setup on Server2. One you’re done, you simply change a single line in your reverse proxy, and now requests are sent to Server2. Assuming the reverse proxy is setup correctly, you should have absolutely no downtime.</p>
<p>But perhaps the biggest advantage of having a reverse proxy, is that you can have services running on a multitude of ports, but you only have to open ports 80 and 443, HTTP and HTTPS respectively. All requests will be coming into your network on those two ports, and the reverse proxy will take care of the rest. All of this will make sense once we start setting the proxy up.</p>
<h2 id="heading-setting-up-the-container">Setting Up the Container</h2>
<h3 id="heading-what-to-do">What to Do</h3>
<p><code>docker-compose.yaml</code>:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">version:</span> <span class="hljs-string">'3'</span>

<span class="hljs-attr">services:</span>
  <span class="hljs-attr">reverse:</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">reverse</span>
    <span class="hljs-attr">hostname:</span> <span class="hljs-string">reverse</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">nginx</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-number">80</span><span class="hljs-string">:80</span>
      <span class="hljs-bullet">-</span> <span class="hljs-number">443</span><span class="hljs-string">:443</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">&lt;path/to/your/config&gt;:/etc/nginx</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">&lt;path/to/your/certs&gt;:/etc/ssl/private</span>
</code></pre>
<p>First of all, you should add a new service to your docker-compose file. You can call it whatever you prefer, in this case I’ve chosen <em>reverse</em>. Here I’ve just chosen <em>nginx</em> as the image, however in a production environment, it’s usually a good idea to specify a version in case there are ever any breaking changes in future updates.</p>
<p>Then you should volume bind two folders. <em>/etc/nginx</em> is where all your configuration files are stored, and <em>/etc/ssl/private</em> is where your SSL certificates are stored. It is VERY important that your config folder does NOT exist on your host first time you’re starting the container. When you start your container through docker-compose, it will automatically create the folder and populate it with the contents of the container. If you have created an empty config folder on your host, it will mount that, and the folder inside the container will be empty.</p>
<h3 id="heading-why-it-works">Why it Works</h3>
<p>There isn’t much to this part. Mostly it’s like starting any other container with docker-compose. What you should notice here is that you are binding port 80 and 443. This is where all requests will come in, and they will be forwarded to whatever service you will specify.</p>
<h2 id="heading-configuring-nginx">Configuring Nginx</h2>
<h3 id="heading-what-to-do-1">What to Do</h3>
<p>Now you should have a config folder on your host. Changing to that directory, you should see a bunch of different files and a folder called <code>conf.d</code>. It’s inside <code>conf.d</code> that all your configuration files will be placed. Right now there’s a single <code>default.conf</code> file, you can go ahead and delete that.</p>
<p>Still inside <code>conf.d</code>, create two folders: <code>sites-available</code> and <code>sites-enabled</code>. Navigate into <code>sites-available</code> and create your first configuration file. Here we’re going to setup an entry for <a target="_blank" href="https://plex.tv">Plex</a>, but feel free to use another service that you have set up if you like. It doesn’t really matter what the file is called, however I prefer to name it like <code>plex.conf</code>.</p>
<p>Now open the file, and enter the following:</p>
<pre><code>upstream plex {
  server        plex:<span class="hljs-number">32400</span>;
}

server {
  listen        <span class="hljs-number">80</span>;
  server_name   plex.example.com;

  location / {
    proxy_pass  http:<span class="hljs-comment">//plex;</span>
  }
}
</code></pre><p>Go into the <code>sites-enabled</code> directory, and enter the following command:</p>
<pre><code>ln -s ../sites-available/plex.conf .
</code></pre><p>This will create a symbolic link to the file in the other folder. Now there’s only one thing left, and that is to change the <code>nginx.conf</code> file in the config folder. If you open the file, you should see the following as the last line:</p>
<pre><code>include /etc/nginx/conf.d<span class="hljs-comment">/*.conf;</span>
</code></pre><p>Change that to:</p>
<pre><code>include /etc/nginx/conf.d/sites-enabled<span class="hljs-comment">/*.conf;</span>
</code></pre><p>In order to get the reverse proxy to actually work, we need to reload the nginx service inside the container. From the host, run <code>docker exec &lt;container-name&gt; nginx -t</code>. This will run a syntax checker against your configuration files. This should output that the syntax is ok. Now run <code>docker exec &lt;container-name&gt; nginx -s reload</code>. This will send a signal to the nginx process that it should reload, and congratulations! You now have a running reverse proxy, and should be able to access your server at <em>plex.example.com</em> (assuming that you have forwarded port 80 to your host in your router).</p>
<p>Even though your reverse proxy is working, you are running on HTTP, which provides no encryption whatsoever. The next part will be how to secure your proxy, and get a perfect score on <a target="_blank" href="https://www.ssllabs.com/ssltest/analyze.html">SSL Labs</a>.</p>
<h3 id="heading-why-it-works-1">Why it Works</h3>
<p><strong>The Configuration File</strong></p>
<p>As you can see, the <code>plex.conf</code> file consists of two parts. An <code>upstream</code> part and a <code>server</code> part. Let’s start with the <code>server</code> part. This is where you are defining the port receiving the incoming requests, what domain this configuration should match, and where it should be sent to.</p>
<p>The way this server is being set up, you should make a file for each service that you want to proxy requests to, so obviously you need some way to distinguish which file to receive each request. This is what the <code>server-name</code> directive does. Below that we have the <code>location</code> directive.</p>
<p>In our case we only need one <code>location</code>, however you can have as many <code>location</code> directives as you want. Imagine you have a website with a frontend and a backend. Depending on the infrastructure you’re using, you’ll have the frontend as one container and the backend as another container. You could then have <code>location / {}</code> which will send requests to the frontend, and <code>location /api/ {}</code> which will send requests to the backend. Suddenly you have multiple services running on a single memorable domain.</p>
<p>As for the <code>upstream</code> part, that can be used for load-balancing. If you’re interested in learning more about how that works, you can look at the <a target="_blank" href="http://nginx.org/en/docs/http/ngx_http_upstream_module.html">official docs here</a>. For our simple case, you just define the hostname or ip address of the service you want to proxy to, and what port is should be proxied to, and then refer to the upstream name in the <code>location</code> directive.</p>
<p><strong>Hostname Vs. IP Address</strong></p>
<p>To understand what a hostname is, let’s make an example. Say you are on your home network <em>192.168.1.0.</em> You then set up a server on <em>192.168.1.10</em> and run Plex on it. You can now access Plex on <em>192.168.1.10:32400</em>, as long as you are still on the same network. Another possibility is to give the server a hostname. In this case we’ll give it the hostname <em>plex</em>. Now you can access Plex by entering <em>plex:32400</em> in your browser!</p>
<p>This same concept was introduced to docker-compose in version 3. If you look at the docker-compose file earlier in this article, you’ll notice that I gave it a <code>hostname: reverse</code> directive. Now all other containers can access my reverse proxy by its hostname. One thing that’s very important to note, is that the service name has to be the same as the hostname. This is something that the creators of docker-compose chose to impose.</p>
<p>Another really important thing to remember, is that by default docker containers are put on their own network. This means that you won’t be able to access your container by it’s hostname, if you’re sitting on your laptop on your host network. It is only the containers that are able to access each other through their hostname.</p>
<p>So to sum it up and make it really clear. In your docker-compose file, add the <code>hostname</code> directive to your services. Most of the time your containers will get a new IP every time you restart the container, so referring to it via hostname, means it doesn’t matter what IP your container is getting.</p>
<p><strong>Sites-available &amp; Sites-enabled</strong></p>
<p>Why are we creating the <code>sites-available</code> and <code>sites-enabled</code> directories? This is not something of my creation. If you install Nginx on a server, you will see that it comes with these folders. However because Docker is built with microservices in mind, where one container should only ever do one thing, these folders are omitted in the container. We’re recreating them again, because of how we’re using the container.</p>
<p>And yes, you could definitely just make a <code>sites-enabled</code> folder, or directly host your configuration files in <code>conf.d</code>. Doing it this way, enables you to have passive configuration laying around. Say that you are doing maintenance, and don’t want to have the service active; you simply remove the symbolic link, and put it back when you want the service active again.</p>
<p><strong>Symbolic Links</strong></p>
<p>Symbolic links are a very powerful feature of the operating system. I had personally never used them before setting up an Nginx server, but since then I’ve been using them everywhere I can. Say you are working on 5 different projects, but all these projects use the same file in some way. You can either copy the file into every project, and refer to it directly, or you can place the file in one place, and in those 5 projects make symlinks to that file.</p>
<p>This gives two advantages: you take up 4 times less space than you otherwise would have, and then the most powerful of them all; change the file in one place, and it changes in all 5 projects at once! This was a bit of a sidestep, but I think it’s worth mentioning.</p>
<h2 id="heading-securing-nginx-proxy">Securing Nginx Proxy</h2>
<h3 id="heading-what-to-do-2">What to Do</h3>
<p>Go to your config folder, and create 3 files and fill them with the following input:</p>
<p><code>common.conf</code>:</p>
<pre><code>add_header Strict-Transport-Security    <span class="hljs-string">"max-age=31536000; includeSubDomains"</span> always;
add_header X-Frame-Options              SAMEORIGIN;
add_header X-Content-Type-Options       nosniff;
add_header X-XSS-Protection             <span class="hljs-string">"1; mode=block"</span>;
</code></pre><p><code>common_location.conf</code>:</p>
<pre><code>proxy_set_header    X-Real-IP           $remote_addr;
proxy_set_header    X-Forwarded-For     $proxy_add_x_forwarded_for;
proxy_set_header    X-Forwarded-Proto   $scheme;
proxy_set_header    Host                $host;
proxy_set_header    X-Forwarded-Host    $host;
proxy_set_header    X-Forwarded-Port    $server_port;
</code></pre><p><code>ssl.conf</code>:</p>
<pre><code>ssl_protocols               TLSv1 TLSv1<span class="hljs-number">.1</span> TLSv1<span class="hljs-number">.2</span>;
ssl_ecdh_curve              secp384r1;
ssl_ciphers                 <span class="hljs-string">"ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384 OLD_TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 OLD_TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256"</span>;
ssl_prefer_server_ciphers   on;
ssl_dhparam                 /etc/nginx/dhparams.pem;
ssl_certificate             /etc/ssl/private/fullchain.pem;
ssl_certificate_key         /etc/ssl/private/privkey.pem;
ssl_session_timeout         <span class="hljs-number">10</span>m;
ssl_session_cache           shared:SSL:<span class="hljs-number">10</span>m;
ssl_session_tickets         off;
ssl_stapling                on;
ssl_stapling_verify         on;
</code></pre><p>Now open the <code>plex.conf</code> file, and change it to the following (notice lines 6, 9, 10 &amp; 14):</p>
<pre><code>upstream plex {
  server        plex:<span class="hljs-number">32400</span>;
}

server {
  listen        <span class="hljs-number">443</span> ssl;
  server_name   plex.example.com;

  include       common.conf;
  include       /etc/nginx/ssl.conf;

  location / {
    proxy_pass  http:<span class="hljs-comment">//plex;</span>
    include     common_location.conf;
  }
}
</code></pre><p>Now go back to the root of your config folder, and run the following command:</p>
<pre><code>openssl dhparam -out dhparams.pem <span class="hljs-number">4096</span>
</code></pre><p>This will take a long time to complete, even up to an hour in some cases.</p>
<p>If you followed my article on getting a LetsEncrypt SSL Certificate, your certificates should be located in <code>&lt;/path/to/your/letsencrypt/config&gt;/etc/letsencrypt/live/&lt;domain&gt;/</code> .</p>
<p>When I helped a friend set this up on his system, we ran into some problems where it couldn’t open the files when they were located in that directory. Most likely the cause of some permissions problems. The easy solution to this is to make an SSL directory, like <code>&lt;/path/to/your/nginx/config&gt;/certs</code>, and then mount that to the Nginx container’s <code>/etc/ssl/private</code> folder. In the newly created folder, you should then make symbolic links, to the certs in your LetsEncrypt’s config folder.</p>
<p>When the <code>openssl</code> command is done running, you should run the <code>docker exec &lt;container-name&gt; nginx -t</code> to make sure that all the syntax is correct, and then reload it by running <code>docker exec &lt;container-name&gt; nginx -s reload</code>. At this point everything should be running, and you now have a working and perfectly secure reverse proxy!</p>
<h3 id="heading-why-it-works-2">Why it Works</h3>
<p>Looking in the <code>plex.conf</code> file, there is only one major change, and that is what port the reverse proxy is listening on, and telling it that it’s an ssl connection. Then there are 3 places where we’re including the 3 other files we made. While SSL is kind of secure by itself, these other files make it even more secure. However if for some reason you don’t want to include these files, you need to move the <code>ssl-certificate</code> and <code>ssl-certificate-key</code>inside the <code>.conf</code> file. These are required to have, in order for an HTTPS connection to work.</p>
<p><strong>Common.conf</strong></p>
<p>Looking in the <code>common.conf</code> file, we add 4 different headers. Headers are something that the server sends to the browser on every response. These headers tell the browser to act a certain way, and it is then up to the browser to enforce these headers.</p>
<p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security"><strong>Strict-Transport-Security (HSTS)</strong></a></p>
<p>This header tells the browser that connections should be made over HTTPS. When this header has been added, the browser won’t let you make plain HTTP connection to the server, ensuring that all communication is secure.</p>
<p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options"><strong>X-Frame-Options</strong></a></p>
<p>When specifying this header, you are specifying whether or not other sites can embed your content into their sites. This can help avoid <a target="_blank" href="https://en.wikipedia.org/wiki/Clickjacking">clickjacking</a> attacks.</p>
<p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options"><strong>X-Content-Type-Options</strong></a></p>
<p>Say you have a site where users can upload files. There’s not enough validation on the files, so a user successfully uploads a <code>php</code> file to the server, where the server is expecting an image to be uploaded. The attacker may then be able to access the uploaded file. Now the server responds with an image, however the file’s MIME-type is <code>text/plain</code>. The browser will ‘sniff’ the file, and then render the php script, allowing the attacker to do RCE (Remote Code Execution).</p>
<p>With this header set to ‘nosniff’, the browser will not look at the file, and simply render it as whatever the server tells the browser that it is.</p>
<p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection"><strong>X-XSS-Protection</strong></a></p>
<p>While this header was more necessary in older browsers, it’s so easy to add that you might as well. Some XSS (Cross-site Scripting) attacks can be very intelligent, while some are very rudimentary. This header will tell browsers to scan for the simple vulnerabilities and block them.</p>
<p><strong>Common_location.conf</strong></p>
<p><strong>X-Real-IP</strong></p>
<p>Because your servers are behind a reverse proxy, if you try to look at the requesting IP, you will always see the IP of the reverse proxy. This header is added so you can see which IP is actually requesting your service.</p>
<p><strong>X-Forwarded-For</strong></p>
<p>Sometimes a users request will go through multiple clients before it reaches your server. This header includes an array of all those clients.</p>
<p><strong>X-Forwarded-Proto</strong></p>
<p>This header will show what protocol is being used between client and server.</p>
<p><strong>Host</strong></p>
<p>This ensures that it’s possible to do a reverse DNS lookup on the domain name. It’s used when the <code>server_name</code> directive is different than what you are proxying to.</p>
<p><strong>X-Forwarded-Host</strong></p>
<p>Shows what the real host of the request is instead of the reverse proxy.</p>
<p><strong>X-Forwarded-Port</strong></p>
<p>Helps identify what port the client requested the server on.</p>
<p><strong>Ssl.conf</strong></p>
<p>SSL is a huge topic in and of itself, and too big to start explaining in this article. There are many great tutorials out there on how SSL handshakes work, and so on. If you want to look into this specific file, I suggest looking at the protocols and ciphers being used, and what difference they make.</p>
<h2 id="heading-redirecting-http-to-https">Redirecting HTTP to HTTPS</h2>
<p>The observant ones have maybe noticed that we are only ever listening on port 443 in this secure version. This would mean that anyone trying to access the site via <em>https://*</em> would get through, but trying to connect through <em>http://*</em> would just get an error. Luckily there’s a really easy fix to this. Make a <code>redirect.conf</code> file with the following contents:</p>
<pre><code>server {
  listen        <span class="hljs-number">80</span>;

  server_name   _;

  <span class="hljs-keyword">return</span> <span class="hljs-number">301</span> https:<span class="hljs-comment">//$host$request_uri;</span>
}
</code></pre><p>Now just make sure that it appears in your <code>sites-enabled</code> folder, and when you’ve reloaded the Nginx process in the container, all requests to port 80 will be redirected to port 443 (HTTPS).</p>
<h2 id="heading-final-thoughts">Final Thoughts</h2>
<p>Now that your site is up and running, you can head over to <a target="_blank" href="https://www.ssllabs.com/ssltest/analyze.html">SSL Labs</a> and run a test to see how secure your site is. At the time of writing this, you should get a perfect score. However there is a big thing to notice about that.</p>
<p>There will always be a balance between security and convenience. In this case the weights are heavily on the side of security. If you run the test on SSL Labs and scroll down, you will see there are multiple devices that won’t be able to connect with your site, because they don’t support new standards.</p>
<p>So have this in mind when you are setting this up. Right now I am just running a server at home, where I don’t have to worry about that many people being able to access it. But if you do a scan on Facebook, you’ll see they won’t have as great a score, however their site can be accessed by more devices.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to setup your website for that sweet, sweet HTTPS with Docker, Nginx, and letsencrypt ]]>
                </title>
                <description>
                    <![CDATA[ By Russell Hammett Jr. (Kritner) I’ve used letsencrypt in the past for free certs. I have not successfully utilized it since moving over to docker/kestrel/nginx. That all changed today, and I had a hell of a time figuring out what I was doing to get ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/docker-compose-nginx-and-letsencrypt-setting-up-website-to-do-all-the-things-for-that-https-7cb0bf774b7e/</link>
                <guid isPermaLink="false">66c34967c8f6b2d81069b347</guid>
                
                    <category>
                        <![CDATA[ Docker ]]>
                    </category>
                
                    <category>
                        <![CDATA[ nginx ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Software Engineering ]]>
                    </category>
                
                    <category>
                        <![CDATA[ technology ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Wed, 19 Sep 2018 18:48:35 +0000</pubDate>
                <media:content url="https://cdn-media-1.freecodecamp.org/images/0*m-xEibEV8ttbhv7W.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Russell Hammett Jr. (Kritner)</p>
<p>I’ve used <a target="_blank" href="https://letsencrypt.org/">letsencrypt</a> in the past for free certs. I have not successfully utilized it since moving over to docker/kestrel/nginx. That all changed today, and I had a hell of a time figuring out what I was doing to get it working.</p>
<p>This whole Unix, docker, nginx, stuff is pretty new (to me), so maybe it’s just something simple I was missing the whole time. Nonetheless, I’m hoping this will help someone else, or me several months down the road if I decide to do it again.</p>
<h4 id="heading-original-setup">Original Setup</h4>
<p>I have a <a target="_blank" href="https://www.microsoft.com/net/download">.net core</a> website, being hosted via <a target="_blank" href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/servers/kestrel?view=aspnetcore-2.1">kestrel</a>, running on <a target="_blank" href="https://www.docker.com/">docker</a>, with a reverse proxy via <a target="_blank" href="https://www.nginx.com/">nginx</a>. Up until now, that reverse proxying from nginx was only working over http/port 80. I don’t know a whole lot about reverse proxies. From the sound of it, it can take in requests, and forward them to a specific location on behalf of the requester. In my case, the nginx container receives http requests, and nginx forwards that request onto my kestrel hosted .net core site. Is that right? Hopefully!</p>
<p>As mentioned previously, the nginx was only working with http traffic. I was having a lot of trouble getting it working with https, the original configuration is as follows:</p>
<p>docker-compose:</p>
<pre><code>version: <span class="hljs-string">'3.6'</span>services:    kritner-website-web:    image: ${DOCKER_REGISTRY}/kritnerwebsite    expose:      - <span class="hljs-string">"5000"</span>    networks:      - frontend    restart: always    container_name: kritnerwebsite_web  kritner-website-nginx:    image: nginx:latest    ports:      - <span class="hljs-string">"80:80"</span>    volumes:      - ../src/nginx/nginx.conf:<span class="hljs-regexp">/etc/</span>nginx/nginx.conf    depends_on:      - kritner-website-web    networks:      - frontend    restart: always    container_name: kritnerwebsite_nginx
</code></pre><pre><code>networks:  frontend:
</code></pre><p>In the docker-compose file, I’m using two separate containers — the website, which exposes port 5000 (on the docker network, not publicly), and nginx which operates on port 80.</p>
<p>nginx.conf</p>
<pre><code>worker_processes <span class="hljs-number">4</span>; events { worker_connections <span class="hljs-number">1024</span>; } http {    sendfile on;     upstream app_servers {        server kritner-website-web:<span class="hljs-number">5000</span>;    }     server {        listen <span class="hljs-number">80</span>;         location / {            proxy_pass         http:<span class="hljs-comment">//app_servers;            proxy_redirect     off;            proxy_set_header   Host $host;            proxy_set_header   X-Real-IP $remote_addr;            proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;            proxy_set_header   X-Forwarded-Host $server_name;        }    }}</span>
</code></pre><p>In the config file, we’re setting up an upstream server with the same name that we’re calling our container service from the docker-compose file <code>kritner-website-web:5000</code>.</p>
<p>Note, all the above can be found at this <a target="_blank" href="https://github.com/Kritner/KritnerWebsite/tree/0e86849c97bdcabf68d0df7ed7eb7e5eebdccd4f">commit point</a> on my website’s repository.</p>
<h4 id="heading-enter-https">Enter HTTPS</h4>
<p>Letsencrypt is a certificate authority that offers free certs to help secure your website. Why is HTTPS via TLS important? Well, there’s a whole lot to that, and how it works. The basic idea is the user’s traffic is encrypted on either end prior to being sent to the other end. This means if you’re on public wifi, and on https, someone that was “sniffing the wire” so to speak, would see that traffic is occurring, but not the content of said traffic. Since both ends are encrypting/decrypting said traffic with the same encryption key.</p>
<p>If you were on an http site, this traffic would be sent back and forth in plain text. Meaning your data is in danger of being eavesdropped on! Maybe I’ll write a bit more about encryption at some point. (<em>note to self</em>) Especially since it’s something I’m doing as my day job!</p>
<p>letsencrypt is a service I’ve used before. There are various implementations to try to make it as easy as possible to use. Through research for this post, I happened upon <a target="_blank" href="https://letsencrypt.org/docs/client-options/">this</a>.</p>
<p>Although I hadn’t found this page until now, it would have been useful prior to beginning my adventure. I wanted to use letsencrypt along with my docker container website, and nginx, with as little maintenance as possible. letsencrypt certificates are only good for 90 days.</p>
<p>In my research, I happened upon a docker image <a target="_blank" href="https://hub.docker.com/r/linuxserver/letsencrypt/">linuxserver/letsencrypt</a> that promises to utilize nginx, letsencrypt certificate generation, AND auto renewal. Sounds awesome! While the documentation of the image seems mostly adequate — for someone well versed in all this process. I found it to be lacking. The whole setup process took me some time to figure out. Hence this post, to hopefully help out the next person, or again me in the future!</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/uDVdXYEOt66dbf284uuJT0gybayII2EW3lTF" alt="Image" width="800" height="166" loading="lazy">
<em>Linux Server.IO logo</em></p>
<h4 id="heading-struggles">Struggles</h4>
<p>The things I most struggled with when getting this linuxserver/letsencrypt image up and working were:</p>
<ul>
<li>How docker volumes “work” and their relationship with this container</li>
<li>How to set volumes up to utilize my configuration (related to the above point) — I was initially having a lot of trouble figuring out why my settings changed on the container were being changed back on reloading of said container (because that’s what they’re supposed to do)</li>
<li>How to set up the correct nginx configuration — where to put it, and what to put in it.</li>
</ul>
<h4 id="heading-docker-volumes">Docker volumes</h4>
<p>Docker volumes (<a target="_blank" href="https://docs.docker.com/storage/volumes/">doc</a>):</p>
<blockquote>
<p>Volumes are the preferred mechanism for persisting data generated by and used by Docker containers. While <a target="_blank" href="https://docs.docker.com/storage/bind-mounts/">bind mounts</a> are dependent on the directory structure of the host machine, volumes are completely managed by Docker. Volumes have several advantages over bind mounts</p>
</blockquote>
<p>letsencrypt has a lot of configuration to go along with it. It took a while for me to realize, but I needed a volume that mapped from a directory on the <strong>docker host</strong> to a specific directory on the letsencrypt image. I eventually accomplished this in the compose file like so:</p>
<pre><code>volumes:      - ${DOCKER_KRITNER_NGINX}:<span class="hljs-regexp">/config       - ./</span>nginx.conf:<span class="hljs-regexp">/config/</span>nginx/site-confs/<span class="hljs-keyword">default</span>
</code></pre><p>The first item in the array (<code>${DOCKER_KRITNER_NGINX}:/config</code>) takes a new environment variable that maps the host directory (defined in the variable) to the <code>/config</code> within the docker container itself. This means that the <strong>docker host</strong> (at the env var path) will contain the same config as the <strong>docker container</strong> at the secondary portion of the volume mapping (<code>/config</code>)</p>
<p>The second item (<code>./nginx.conf:/config/nginx/site-confs/default</code>) maps my local repositories nginx.conf file (the file where I set up the reverse proxy) to override the <code>/config/nginx/site-confs/default</code> file on the docker host and container.</p>
<p>The full list of files that I ended up needing to modify for my particular situation was:</p>
<ul>
<li><code>/config/dns-conf/dnsimple.ini</code></li>
<li><code>/config/nginx/site-confs/default</code></li>
</ul>
<p>The <code>dnsimple.ini</code> configuration was add my api key, and the <code>…/default</code> houses the nginx configuration.</p>
<p>The final <code>default</code> configuration I ended up with is:</p>
<pre><code>upstream app_servers {        server kritnerwebsite:<span class="hljs-number">5000</span>;}
</code></pre><pre><code>## Version <span class="hljs-number">2018</span>/<span class="hljs-number">09</span>/<span class="hljs-number">12</span> - Changelog: https:<span class="hljs-comment">//github.com/linuxserver/docker-letsencrypt/commits/master/root/defaults/default</span>
</code></pre><pre><code># listening on port <span class="hljs-number">80</span> disabled by <span class="hljs-keyword">default</span>, remove the <span class="hljs-string">"#"</span> signs to enable# redirect all traffic to httpsserver { listen <span class="hljs-number">80</span>; server_name kritnerwebsite; <span class="hljs-keyword">return</span> <span class="hljs-number">301</span> https:<span class="hljs-comment">//$host$request_uri;}</span>
</code></pre><pre><code># main server blockserver { listen <span class="hljs-number">443</span> ssl;
</code></pre><pre><code># enable subfolder method reverse proxy confs include /config/nginx/proxy-confs<span class="hljs-comment">/*.subfolder.conf;</span>
</code></pre><pre><code># all ssl related config moved to ssl.conf include /config/nginx/ssl.conf;  # enable <span class="hljs-keyword">for</span> ldap auth #include /config/nginx/ldap.conf;
</code></pre><pre><code>client_max_body_size <span class="hljs-number">0</span>;
</code></pre><pre><code>location / {            proxy_pass         http:<span class="hljs-comment">//app_servers;            proxy_redirect     off;            proxy_set_header   Host $host;            proxy_set_header   X-Real-IP $remote_addr;            proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;            proxy_set_header   X-Forwarded-Host $server_name;    }</span>
</code></pre><pre><code>}
</code></pre><pre><code># enable subdomain method reverse proxy confsinclude /config/nginx/proxy-confs<span class="hljs-comment">/*.subdomain.conf;# enable proxy cache for authproxy_cache_path cache/ keys_zone=auth_cache:10m;</span>
</code></pre><p>There are a few changes from the default that was there, which I’ll try to highlight next.</p>
<pre><code>upstream app_servers {        server kritnerwebsite:<span class="hljs-number">5000</span>;}
</code></pre><p>Above is pretty cool, since docker has its own internal DNS (I guess?). You can set up an upstream server by the containers name, in my case “kritnerwebsite”. (Note: I changed it from earlier in the post, which was “kritner-website-web”.)</p>
<pre><code># listening on port <span class="hljs-number">80</span> disabled by <span class="hljs-keyword">default</span>, remove the <span class="hljs-string">"#"</span> signs to enable# redirect all traffic to httpsserver { listen <span class="hljs-number">80</span>; server_name kritnerwebsite; <span class="hljs-keyword">return</span> <span class="hljs-number">301</span> https:<span class="hljs-comment">//$host$request_uri;}</span>
</code></pre><p>Uncommented out this section from the default, applied my server_name of “kritnerwebsite”</p>
<pre><code># main server blockserver { listen <span class="hljs-number">443</span> ssl;
</code></pre><pre><code># enable subfolder method reverse proxy confs include /config/nginx/proxy-confs<span class="hljs-comment">/*.subfolder.conf;</span>
</code></pre><pre><code># all ssl related config moved to ssl.conf include /config/nginx/ssl.conf;  # enable <span class="hljs-keyword">for</span> ldap auth #include /config/nginx/ldap.conf;
</code></pre><pre><code>client_max_body_size <span class="hljs-number">0</span>;
</code></pre><pre><code>location / {            proxy_pass         http:<span class="hljs-comment">//app_servers;            proxy_redirect     off;            proxy_set_header   Host $host;            proxy_set_header   X-Real-IP $remote_addr;            proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;            proxy_set_header   X-Forwarded-Host $server_name;    }</span>
</code></pre><pre><code>}
</code></pre><p>In the above, it’s mostly from the “default” save for “location” and everything within that object. Here, we’re setting up the reverse proxy to forward requests to “/” (anything) to our <code>http://app_servers</code> (kritnerwebsite as per our upstream).</p>
<h4 id="heading-docker-composeyml">docker-compose.yml</h4>
<p>Our docker compose file didn’t change a <em>whole</em> lot from the initial. There were a few notable changes, which I’ll also get into describing:</p>
<pre><code>version: <span class="hljs-string">'3.6'</span>services:    nginx:    image: linuxserver/letsencrypt    ports:      - <span class="hljs-string">"80:80"</span>      - <span class="hljs-string">"443:443"</span>    volumes:      - ${DOCKER_KRITNER_NGINX}:<span class="hljs-regexp">/config       - ./</span>nginx.conf:<span class="hljs-regexp">/config/</span>nginx/site-confs/<span class="hljs-keyword">default</span>    depends_on:      - kritnerwebsite    networks:      - frontend    container_name: nginx    environment:      - PUID=<span class="hljs-number">1001</span> # get on dockerhost through command <span class="hljs-string">"id &lt;user&gt;"</span><span class="hljs-string">"      - PGID=1001      - EMAIL=kritner@gmail.com      - URL=kritner.com      - SUBDOMAINS=www      - TZ=America/NewYork      - VALIDATION=dns # using dns validation      - DNSPLUGIN=dnsimple # via dnsimple, note there is additional configuration require separate from this file      # - STAGING=true # this should be uncommented when testing for initial success, to avoid some rate limiting</span>
</code></pre><pre><code>kritnerwebsite:    image: ${DOCKER_REGISTRY}/kritnerwebsite    networks:      - frontend    expose:      - <span class="hljs-string">"5000"</span>    restart: always    container_name: kritnerwebsite  networks:  frontend:
</code></pre><p>for the new parts:</p>
<pre><code>nginx:    image: linuxserver/letsencrypt
</code></pre><p>Using a different image — linuxserver/letsencrypt instead of nginx. This image has nginx included, but also certbot, along with a cronjob to run certbot at application start.</p>
<pre><code>ports:      - <span class="hljs-string">"80:80"</span>      - <span class="hljs-string">"443:443"</span>
</code></pre><p>Now we’re using both http and https ports (though note, we’re redirecting http calls to https via the nginx config).</p>
<pre><code>volumes:      - ${DOCKER_KRITNER_NGINX}:<span class="hljs-regexp">/config       - ./</span>nginx.conf:<span class="hljs-regexp">/config/</span>nginx/site-confs/<span class="hljs-keyword">default</span>
</code></pre><p>Already discussed earlier in the post, we’re using these volumes to properly set up the nginx configuration, with our dnsimple api key, as well as our reverse proxying to the kritnerwebsite.</p>
<pre><code>environment:      - PUID=<span class="hljs-number">1001</span> # get on dockerhost through command <span class="hljs-string">"id &lt;user&gt;"</span>      - PGID=<span class="hljs-number">1001</span>      - EMAIL=kritner@gmail.com      - URL=kritner.com      - SUBDOMAINS=www      - TZ=America/NewYork      - VALIDATION=dns # using dns validation      - DNSPLUGIN=dnsimple # via dnsimple, note there is additional configuration <span class="hljs-built_in">require</span> separate <span class="hljs-keyword">from</span> <span class="hljs-built_in">this</span> file      # - STAGING=<span class="hljs-literal">true</span> # <span class="hljs-built_in">this</span> should be uncommented when testing <span class="hljs-keyword">for</span> initial success, to avoid some rate limiting
</code></pre><p>Environment variables needed as per the letsencrypt documentation can be found <a target="_blank" href="https://hub.docker.com/r/linuxserver/letsencrypt/">here</a>.</p>
<ul>
<li>PUID/PGID — get on dockerhost through command “id ”</li>
<li>Email — well, your email (used for cert expiration emails apparently)</li>
<li>URL — the main domain URL</li>
<li>subdomains — any subdomains to the URL to be certified</li>
<li>TZ — timezone</li>
<li>Validation — the type of validation to do — I’m using DNSimple, so i needed DNS in this field. Other options are html, tls-sni</li>
<li>dnsplugin — dnsimple — other options are <code>cloudflare</code>, <code>cloudxns</code>, <code>digitalocean</code>, <code>dnsmadeeasy</code>, <code>google</code>, <code>luadns</code>, <code>nsone</code>, <code>rfc2136</code> and <code>route53</code> as per the letsencrypt documentation</li>
<li>Staging=true — I used this for testing out all my various attempts prior to getting it working. letsencrypt has rate limiting when not running in staging mode (or at least in staging it’s harder to run up against).</li>
</ul>
<p>All the above changes, experimenting, failing, and then finally succeeding can be found in <a target="_blank" href="https://github.com/Kritner/KritnerWebsite/pull/24/commits">this pull request</a>.</p>
<p>The final result?</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/8p9oqF7gDeWHXY9oqutUsEB5rmbmH8zh9D8s" alt="Image" width="281" height="43" loading="lazy">
<em>Awww yeah</em></p>
<p>and from <a target="_blank" href="https://www.ssllabs.com/">https://www.ssllabs.com/</a> —</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/YcQFIv8RixHyyhSlurfdIZVZmbttLUOQi5V0" alt="Image" width="800" height="359" loading="lazy"></p>
<p>Not an “A+”, but really not bad for using one pre-built docker image for my HTTPs needs!</p>
<p>Related:</p>
<ul>
<li><a target="_blank" href="https://medium.com/@kritner/going-from-an-a-to-an-a-on-ssllabs-com-570d2e245100">Going from an “A” to an “A+” on ssllabs.com</a></li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to host multiple domain names and projects on one server ]]>
                </title>
                <description>
                    <![CDATA[ By BinHong Lee NGINX is one magical tool _Photo by [Unsplash](https://unsplash.com/@imgix?utm_source=medium&utm_medium=referral" rel="noopener" target="_blank" title="">imgix on <a href="https://unsplash.com?utm_source=medium&utm_medium=referral" re... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-you-can-host-multiple-domain-names-and-projects-in-one-vps-7aed4f56e7a1/</link>
                <guid isPermaLink="false">66c356bffd47bc6e657cb87d</guid>
                
                    <category>
                        <![CDATA[ nginx ]]>
                    </category>
                
                    <category>
                        <![CDATA[ General Programming ]]>
                    </category>
                
                    <category>
                        <![CDATA[ servers ]]>
                    </category>
                
                    <category>
                        <![CDATA[ tech  ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Hosting ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Wed, 29 Aug 2018 16:50:07 +0000</pubDate>
                <media:content url="https://cdn-media-1.freecodecamp.org/images/0*FT9uL7NRg2iep6-e" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By BinHong Lee</p>
<h4 id="heading-nginx-is-one-magical-tool">NGINX is one magical tool</h4>
<p><img src="https://cdn-media-1.freecodecamp.org/images/0*FT9uL7NRg2iep6-e" alt="Image" width="800" height="476" loading="lazy">
_Photo by [Unsplash](https://unsplash.com/@imgix?utm_source=medium&amp;utm_medium=referral" rel="noopener" target="_blank" title=""&gt;imgix on &lt;a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral" rel="noopener" target="<em>blank" title=")</em></p>
<p>I own multiple domain names, and each one hosts a different side project. For the longest time, everything that required ‘hosting’ was hosted on Heroku. But their free tier can be quite limited, it can also get costly quickly if you are paying for each separate project. So instead, I decided to explore putting all of them together using NGINX (recommended to me by <a target="_blank" href="https://www.freecodecamp.org/news/how-you-can-host-multiple-domain-names-and-projects-in-one-vps-7aed4f56e7a1/undefined">Jane Manchun Wong</a>).</p>
<h3 id="heading-required-resources">Required Resources</h3>
<h4 id="heading-virtual-private-server-vps">Virtual Private Server (VPS)</h4>
<p>You’ll need a virtual server such as <a target="_blank" href="https://www.freecodecamp.org/news/how-you-can-host-multiple-domain-names-and-projects-in-one-vps-7aed4f56e7a1/undefined">DigitalOcean</a> or EC2 by <a target="_blank" href="https://www.freecodecamp.org/news/how-you-can-host-multiple-domain-names-and-projects-in-one-vps-7aed4f56e7a1/undefined">AWS</a>. Personally I uses <a target="_blank" href="https://www.vultr.com/?ref=7358373">Vultr</a> (here’s the <a target="_blank" href="http://vultr.com/">non-referral link</a>) which costs me about $2.50 / month.</p>
<h4 id="heading-domain-names">Domain Names</h4>
<p>You will need to register a few domain names. Assuming that you probably already have them, make sure your domain names are pointing at the name servers of your VPS. There should be a DNS section in your domain name service dashboard where you can select “custom DNS” or something similar. If you are not sure what the nameservers of your VPS are, you should be able to find that info easily through a simple search of “nameserver” + VPS service name.</p>
<h3 id="heading-setting-up-nginx">Setting up NGINX</h3>
<h4 id="heading-installation-and-basic-setup">Installation and basic setup</h4>
<p><em>Reference from <a target="_blank" href="https://www.digitalocean.com/community/tutorials/how-to-install-nginx-on-ubuntu-16-04">How To Install Nginx on Ubuntu 16.04</a></em></p>
<p>Run the following commands through SSH-ing into the VPS. It will install NGINX, set firewall rules allowing it, and set NGINX to autostart on boot.</p>
<h4 id="heading-configuration-setup">Configuration setup</h4>
<p><em>Reference from <a target="_blank" href="https://geekflare.com/multiple-domains-on-one-server-with-apache-nginx/">Host Multiple Domains on One Server/IP with Apache or nginx</a></em></p>
<p>The default virtual.conf location should be at /etc/nginx/conf.d/virtual.conf. I recommend backing up the default file before making any changes. (If it doesn’t exist, you can just create it.) Edit the file to look something like the following:</p>
<p>Here are a few things to look at:</p>
<ul>
<li><em>server</em> block — Each of these should represent each different domain or subdomain in use.</li>
<li><em>root</em> — This is the location where the (HTML) files are loaded from.</li>
<li>_server<em>name</em> — (sub)domain name(s) that should load these specific files.</li>
<li>_proxy<em>redirect</em> — in cases where you are redirecting a specific subdomain to an active server, you will want to add this and put the IP location after it. (For local servers, either <a target="_blank" href="http://127.0.0.1:port"><em>http://127.0.0.1:port</em></a> or <a target="_blank" href="http://localhost:port"><em>http://localhost:port</em></a> should work as intended.)</li>
</ul>
<pre><code>sudo systemctl restart nginx
</code></pre><p>After you are done, restart the server so the new configurations will be loaded and applied.</p>
<h3 id="heading-cloning-and-linking">Cloning and linking</h3>
<p>Now remember, since you have your directory pointing at /opt/htdocs/<em>websiteName</em>, your initial thought might be to clone your projects into these folders. This can work, but it’s not ideal since many operations in these folders require root access to really do anything.</p>
<p>Instead, you can clone them into your user folder or anywhere else like you normally would, and then create a soft link to connect the path to your repository folder. Something like this:</p>
<pre><code>git clone git@github.com:binhonglee/binhonglee.github.io ~<span class="hljs-regexp">/websitesudo ln -s ~/</span>website /opt/htdocs/binhong
</code></pre><p>Of course, when you are cloning a Node.js static site folder (ReactJS, Angular or Vue.js), you will want to install (<code>npm install</code>) and build (<code>npm run-script build</code>) them. Then link the <em>./build</em> folder instead of the base level of the cloned repository. (Similarly for Jekyll sites, but use the _./<em>output</em> folder instead.) As for active servers, just make sure your server is running on the same port as it is listed in the configuration file.</p>
<h3 id="heading-set-up-https-with-certbot">Set up HTTPS with certbot</h3>
<p>Thanks to Let’s Encrypt, you can now get free and easy HTTPS certificates. With the introduction of certbot, everything just got even easier!</p>
<p><em>Reference from <a target="_blank" href="https://www.digitalocean.com/community/tutorials/how-to-secure-nginx-with-let-s-encrypt-on-ubuntu-16-04">How To Secure Nginx with Let’s Encrypt on Ubuntu 16.04</a></em></p>
<p>Just run the above for all your domain and subdomain names and certbot will take care of everything. If you were to renew the certs, you can run the following so the certbot will help you renew your SSL certificate.</p>
<pre><code>sudo certbot renew --dry-run
</code></pre><h3 id="heading-updating-everything">Updating everything</h3>
<p>Now that you have everything up and running, you might be thinking, well there seems to be an awful lot to remember if/when I need to update something. Unfortunately, that’s kinda true, but we can always make it easier by adding a script that does it for us.</p>
<p>Here is how one would look:</p>
<p>Thanks for reading! Let me know if you have any questions in the comments below.</p>
<h3 id="heading-about-me">About me</h3>
<p>At the time of writing, I work at Apple Inc. in the role of Siri Language Engineer as an Independent Contractor through AdvantisGlobal. I spend a lot of my free time experimenting and building new things with technologies I find fun and interesting. Follow my exploration journey <a target="_blank" href="https://binhong.me/blog">here</a> or on <a target="_blank" href="https://github.com/binhonglee">GitHub</a>.</p>
<h3 id="heading-other-references">Other References</h3>
<ul>
<li><a target="_blank" href="https://serverfault.com/questions/363159/nginx-proxy-pass-redirects-ignore-port">nginx proxy pass redirects ignore port</a> on <a target="_blank" href="https://serverfault.com">serverfault</a></li>
<li><a target="_blank" href="https://superuser.com/questions/632205/continue-ssh-background-task-jobs-when-closing-ssh">Continue SSH background task/jobs when closing SSH</a> on <a target="_blank" href="https://superuser.com/">superuser</a></li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to create a Django server running uWSGI, NGINX and PostgreSQL on AWS EC2 with Python 3.6 ]]>
                </title>
                <description>
                    <![CDATA[ By Sumeet Kumar Getting a server up and running for a new project every time might be time-consuming or difficult for new developers. So I thought I’d write a step-by-step guide that will ease the deployment process. If you’re in no mood to read, you... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/django-uwsgi-nginx-postgresql-setup-on-aws-ec2-ubuntu16-04-with-python-3-6-6c58698ae9d3/</link>
                <guid isPermaLink="false">66c349512daea10bbc119308</guid>
                
                    <category>
                        <![CDATA[ AWS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Django ]]>
                    </category>
                
                    <category>
                        <![CDATA[ nginx ]]>
                    </category>
                
                    <category>
                        <![CDATA[ General Programming ]]>
                    </category>
                
                    <category>
                        <![CDATA[ tech  ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Sun, 26 Aug 2018 18:38:32 +0000</pubDate>
                <media:content url="https://cdn-media-1.freecodecamp.org/images/1*RoxYjB7zefsqzjUMLLaprQ.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Sumeet Kumar</p>
<p>Getting a server up and running for a new project every time might be time-consuming or difficult for new developers. So I thought I’d write a step-by-step guide that will ease the deployment process.</p>
<p><strong>If you’re in no mood to read, you can copy paste each step as described (replace values) and get your server up and running ?</strong></p>
<h4 id="heading-prerequisites">Prerequisites:</h4>
<ol>
<li>Amazon Linux EC2 instance up and running with the associated key pair (<em>ssh access to it</em>).</li>
<li><strong>Port 22, 80</strong> must be open for this instance.</li>
<li>Django application that you want to deploy.</li>
<li>Database settings are configured to use PostgreSQL.</li>
<li><em>requirements.txt</em> is present in your app, having dependencies list to install.</li>
<li>Git repository for your Django app.</li>
</ol>
<h3 id="heading-ssh-amp-update-ubuntu-instance">SSH &amp; update ubuntu instance</h3>
<p>You need to ssh into your EC2 instance, so make sure you have <strong>port 22</strong> open for your instance and then do a update/upgrade.</p>
<pre><code>ssh -i path-to-your-key.pem ubuntu@your-aws-instance-public-ip

sudo apt-get update &amp;&amp; sudo apt-get upgrade -y
</code></pre><h3 id="heading-installing-python36x-on-aws-ec2-ubuntu-1604">Installing Python3.6.x on AWS EC2 (ubuntu 16.04)</h3>
<p>We will download the <strong>tar.xz</strong> file from official site and than manually install it. Before that we need to install some required dependencies.</p>
<h4 id="heading-building-and-installing-dependencies">Building and installing dependencies</h4>
<pre><code>sudo apt install build-essential checkinstall

sudo apt install libreadline-gplv2-dev libncursesw5-dev libssl-dev libsqlite3-dev tk-dev libgdbm-dev libc6-dev libbz2-dev libffi-dev
</code></pre><h4 id="heading-downloading-amp-manually-installing-required-python-version">Downloading &amp; manually installing required Python version</h4>
<pre><code>cd /opt &amp;&amp; sudo wget https:<span class="hljs-comment">//www.python.org/ftp/python/3.6.6/Python-3.6.6.tar.xz</span>

sudo tar -xvf Python<span class="hljs-number">-3.6</span><span class="hljs-number">.6</span>.tar.xz

cd Python<span class="hljs-number">-3.6</span><span class="hljs-number">.6</span>/

sudo ./configure

sudo make &amp;&amp; sudo make install
</code></pre><h4 id="heading-removing-downloaded-file">Removing downloaded file</h4>
<pre><code>sudo rm -rf Python<span class="hljs-number">-3.6</span><span class="hljs-number">.6</span>.tar.xz
</code></pre><h4 id="heading-check-python-version">Check Python version</h4>
<pre><code>python3 -V
&gt; Python <span class="hljs-number">3.6</span><span class="hljs-number">.6</span>
</code></pre><h3 id="heading-setting-up-ubuntu-user-for-our-application">Setting up Ubuntu user for our application</h3>
<p>Django itself is very secure framework, I agree. But web applications are still vulnerable. It is good practice to run your application as system users with limited privileges which has limited access to resources on your server. So in this section, we will be adding a new user &amp; permission group to our EC2 instance.</p>
<h4 id="heading-adding-ubuntu-system-group-groupname-webapps-in-my-case-and-assign-a-user-username-bunny-in-my-case-to-this-group">Adding ubuntu system group ‘groupname’ [webapps in my case] and assign a user ‘username’ [bunny in my case] to this group</h4>
<pre><code>sudo groupadd --system webapps
sudo useradd --system --gid webapps --shell /bin/bash --home /webapps/project_name bunny
</code></pre><p>Note: I am assuming “<strong>project_name</strong>” is the name that you might have used during “<strong>django-admin startproject &lt;na</strong>me&gt;”</p>
<h4 id="heading-create-a-directory-to-store-your-application">Create a directory to store your application</h4>
<p>Create a directory to store your application in /webapps/project_name/. Change the owner of that directory to your application user bunny:</p>
<pre><code>sudo mkdir -p /webapps/project_name/

sudo chown bunny /webapps/project_name/
</code></pre><h4 id="heading-allow-limited-access-to-other-group-users-to-application-directory">Allow limited access to other group users to application directory</h4>
<pre><code>sudo chown -R bunny:users /webapps/project_name

sudo chmod -R g+w /webapps/project_name
</code></pre><h4 id="heading-now-you-can-switch-to-your-user">Now you can switch to your user</h4>
<pre><code>sudo su - bunny

<span class="hljs-comment">// your console will switch to something like this</span>
bunny@ip<span class="hljs-number">-172</span><span class="hljs-number">-31</span><span class="hljs-number">-5</span><span class="hljs-number">-231</span>:~$
</code></pre><p>To switch back to <strong>sudo</strong> user, just do <code>**ctrl+d**</code> and it’ll kill the user terminal.</p>
<h3 id="heading-installing-and-configuring-postgressql">Installing and configuring PostgresSQL</h3>
<h4 id="heading-installing-postgresql-amp-creating-database">Installing PostgreSQL &amp; creating database</h4>
<pre><code>sudo apt install postgresql postgresql-contrib

sudo su - postgres

postgres@ip<span class="hljs-number">-172</span><span class="hljs-number">-31</span><span class="hljs-number">-5</span><span class="hljs-number">-231</span>:~$ psql

postgres=# CREATE DATABASE database_name;
</code></pre><h4 id="heading-changing-default-password-for-postgres-while-in-psql-terminal">Changing default password for postgres while in <strong>psql</strong> terminal</h4>
<pre><code>postgres=# \password
</code></pre><h3 id="heading-deploy-django-app-on-ec2-instance-via-git-in-virtual-environment">Deploy Django app on EC2 instance via Git in virtual environment</h3>
<p>Deploying your app using a virtual environment allows your app and its requirements to be handled separately. It is good practice to keep your app isolated.</p>
<p>Using the environment concept is handy when you are deploying more than one Django app on a single instance to keep them and their dependencies isolated from each other.</p>
<p>We will be creating a <a target="_blank" href="https://docs.python.org/3.6/library/venv.html">virtual environment</a> in our system user (<em>bunny</em>) directory. Before that we will be installing git as a <em>sudo</em> user.</p>
<h4 id="heading-installing-git-and-pulling-your-code-from-git-repo">Installing Git and pulling your code from git repo</h4>
<pre><code>sudo apt-get install git

sudo su - bunny

<span class="hljs-comment">// change to your repo https or ssh link</span>
bunny@ip<span class="hljs-number">-172</span><span class="hljs-number">-31</span><span class="hljs-number">-5</span><span class="hljs-number">-231</span>:~$ git remote add origin 

git@github.com:&lt;user&gt;/&lt;user-repo&gt;.git

bunny@ip-172-31-5-231:~$ git pull origin &lt;branch_name&gt;
</code></pre><p>Note that we haven’t cloned our complete repo here. Instead we manually set our git link and only pulled the branch that we want to deploy to this instance. You may have a different instance for your development, beta, or production ready web app corresponding to each branch on git.</p>
<h4 id="heading-creating-virtual-environment-using-python36-in-current-directory">Creating virtual environment using Python3.6 in current directory</h4>
<pre><code>bunny@ip<span class="hljs-number">-172</span><span class="hljs-number">-31</span><span class="hljs-number">-5</span><span class="hljs-number">-231</span>:~$ python3<span class="hljs-number">.6</span> -m venv .
bunny@ip<span class="hljs-number">-172</span><span class="hljs-number">-31</span><span class="hljs-number">-5</span><span class="hljs-number">-231</span>:~$ source bin/activate
(project_name)bunny@ip<span class="hljs-number">-172</span><span class="hljs-number">-31</span><span class="hljs-number">-5</span><span class="hljs-number">-231</span>:~$ pip install -r requirements.txt
</code></pre><p>At this point, we have successfully set up our project. Now we need to run some <strong>manage.p<em>y</em></strong> command. This will require that we are in the directory where our manage.py is present, or every time we need to give a path to it:</p>
<pre><code>(project_name)bunny@ip<span class="hljs-number">-172</span><span class="hljs-number">-31</span><span class="hljs-number">-5</span><span class="hljs-number">-231</span>:~$ python &lt;path-to-&gt;manage.py migrate

(project_name)bunny@ip<span class="hljs-number">-172</span><span class="hljs-number">-31</span><span class="hljs-number">-5</span><span class="hljs-number">-231</span>:~$ python &lt;path-to-&gt;manage.py createsuperuser

(project_name)bunny@ip<span class="hljs-number">-172</span><span class="hljs-number">-31</span><span class="hljs-number">-5</span><span class="hljs-number">-231</span>:~$ python &lt;path-to-&gt;manage.py collectstatic
</code></pre><p>Note: <code>collectstatic</code> command requires that the STATIC configuration is setup properly. We are not discussing that here, though, as it is not in the scope of this tutorial.</p>
<pre><code>(project_name)bunny@ip<span class="hljs-number">-172</span><span class="hljs-number">-31</span><span class="hljs-number">-5</span><span class="hljs-number">-231</span>:~$ python &lt;path-to-&gt;manage.py runserver <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span>:<span class="hljs-number">8000</span>
</code></pre><p>This will start up the development server on port <code>8000</code>. <strong>Assuming port 8000 is also open for your instance, you can visit your server's domain name or IP address followed by <code>8000</code> in your browser.</strong></p>
<pre><code>http:<span class="hljs-comment">//your_server_domain_or_public_IP:8000</span>
</code></pre><pre><code>http:<span class="hljs-comment">//your_server_domain_or_public_IP:8000/admin</span>
</code></pre><p><strong>Note: Don’t forget to add your domain or IP to ALLOWED_HOST in your settings.py</strong></p>
<h3 id="heading-setting-up-the-uwsgi-application-server">Setting up the uWSGI Application Server</h3>
<p>Now that we’ve got our project set up and ready to go, we can configure uWSGI to serve our app to the web instead of the lightweight development server provided by Django.</p>
<p><strong>If you’re thinking of running the runserver command on a screen, drop it. The dev server with Django is terribly lightweight, highly insecure, and not scalable.</strong></p>
<p>You can install uWSGI either in virtualenv or globally and configure it accordingly.</p>
<p>In this tutorial, we’ll be installing uWSGI in <em>virtualenv</em>. Before we can install uWSGI, we need the Python development files that the software relies on.</p>
<h4 id="heading-installing-uwsgi-along-with-its-dependencies">Installing uWSGI along with its dependencies</h4>
<pre><code>sudo apt-get install python3-dev
</code></pre><pre><code>sudo su - bunny
</code></pre><pre><code>bunny@ip<span class="hljs-number">-172</span><span class="hljs-number">-31</span><span class="hljs-number">-5</span><span class="hljs-number">-231</span>:~$ source bin/activate
</code></pre><pre><code>(project_name)bunny@ip<span class="hljs-number">-172</span><span class="hljs-number">-31</span><span class="hljs-number">-5</span><span class="hljs-number">-231</span>:~$ pip install uwsgi
</code></pre><p>Let’s run the server using uWSGI. This command does the same thing a <em>manage.py runserver</em> would do. You need to replace values accordingly to successfully test with this command.</p>
<pre><code>(project_name)bunny@ip<span class="hljs-number">-172</span><span class="hljs-number">-31</span><span class="hljs-number">-5</span><span class="hljs-number">-231</span>:~$ uwsgi --http :<span class="hljs-number">8000</span> --home &lt;path-to-virtualenv&gt; --chdir &lt;path-to-manage.py-dir&gt; -w &lt;project-name&gt;.wsgi
</code></pre><h4 id="heading-creating-uwsgi-configuration-file">Creating uWSGI configuration file</h4>
<p>Running uWSGI from the command line is only useful for testing. For actual deployment, we will create a <strong>_._ini</strong> file somewhere in our system user directory. This file will contain all the configuration for handling a heavy request load, and can be tweaked accordingly.</p>
<p>Later in this tutorial, we will run uWSGI behind NGINX. NGINX is highly compatible with uWSGI and has built-in support for interacting with uWSGI.</p>
<h4 id="heading-create-a-directory-conf-in-your-system-user-directory-where-you-will-store-uwsgiini">Create a directory <strong>conf</strong> in your system user directory where you will store <strong>uwsgi.ini</strong></h4>
<pre><code>(project_name)bunny@ip<span class="hljs-number">-172</span><span class="hljs-number">-31</span><span class="hljs-number">-5</span><span class="hljs-number">-231</span>:~$ mkdir conf
</code></pre><pre><code>(project_name)bunny@ip<span class="hljs-number">-172</span><span class="hljs-number">-31</span><span class="hljs-number">-5</span><span class="hljs-number">-231</span>:~$ cd conf
</code></pre><pre><code>(project_name)bunny@ip<span class="hljs-number">-172</span><span class="hljs-number">-31</span><span class="hljs-number">-5</span><span class="hljs-number">-231</span>:~$ nano uwsgi.ini
</code></pre><p>Copy the below code from the gist and save it I think the comments are explanatory enough for each option.</p>
<p><strong>NOTE: <code>updateMe</code> is supposed to be you project name. It is the same name you gave above while creating the system user directory, so update accordingly.</strong></p>
<pre><code>[uwsgi]

# telling user to execute file
uid = bunny

# telling group to execute file
gid = webapps

# name <span class="hljs-keyword">of</span> project you during <span class="hljs-string">"django-admin startproject &lt;name&gt;"</span>
project_name = updateMe

# building base path to where project directory is present [In my <span class="hljs-keyword">case</span> <span class="hljs-built_in">this</span> dir is also where my virtual env is]
base_dir = <span class="hljs-regexp">/webapps/</span>%(project_name)

# set PYTHONHOME/virtualenv or setting where my virtual enviroment is
virtualenv = %(base_dir)

# changig current directory to project directory where manage.py is present
chdir = %(base_dir)/src/

# loading wsgi <span class="hljs-built_in">module</span>
<span class="hljs-built_in">module</span> =  %(project_name).wsgi:application

# enabling master process <span class="hljs-keyword">with</span> n numer <span class="hljs-keyword">of</span> child process
master = <span class="hljs-literal">true</span>
processes = <span class="hljs-number">4</span>

# enabling multithreading and assigning threads per process
# enable-threads  = <span class="hljs-literal">true</span>
# threads = <span class="hljs-number">2</span>

# Enable post buffering past N bytes. save to disk all HTTP bodies larger than the limit $
post-buffering = <span class="hljs-number">204800</span>

# Serialize accept() usage (<span class="hljs-keyword">if</span> possibie).
thunder-lock = True


# Bind to the specified socket using <span class="hljs-keyword">default</span> uwsgi protocol.
uwsgi-socket = %(base_dir)/run/uwsgi.sock

# set the UNIX sockets’ permissions to access
chmod-socket = <span class="hljs-number">666</span>

# <span class="hljs-built_in">Set</span> internal sockets timeout <span class="hljs-keyword">in</span> seconds.
socket-timeout = <span class="hljs-number">300</span>

# <span class="hljs-built_in">Set</span> the maximum time (<span class="hljs-keyword">in</span> seconds) a worker can take to reload/shutdown.
reload-mercy = <span class="hljs-number">8</span>

# Reload a worker <span class="hljs-keyword">if</span> its address space usage is higher than the specified value (<span class="hljs-keyword">in</span> megabytes).
reload-on-<span class="hljs-keyword">as</span> = <span class="hljs-number">512</span>

# respawn processes taking more than <span class="hljs-number">50</span> seconds
harakiri = <span class="hljs-number">50</span>

# respawn processes after serving <span class="hljs-number">5000</span> requests
max-requests = <span class="hljs-number">5000</span>

# clear environment on exit
vacuum = <span class="hljs-literal">true</span>

# When enabled (set to True), only uWSGI internal messages and errors are logged.
disable-logging = True

# path to where uwsgi logs will be saved
logto = %(base_dir)/log/uwsgi.log

# maximum size <span class="hljs-keyword">of</span> log file <span class="hljs-number">20</span>MB
log-maxsize = <span class="hljs-number">20971520</span>

# <span class="hljs-built_in">Set</span> logfile name after rotation.
log-backupname = %(base_dir)/log/old-uwsgi.log

# Reload uWSGI <span class="hljs-keyword">if</span> the specified file or directory is modified/touched.
touch-reload = %(base_dir)/src/

# <span class="hljs-built_in">Set</span> the number <span class="hljs-keyword">of</span> cores (CPUs) to allocate to each worker process.
# cpu-affinity = <span class="hljs-number">1</span>

# Reload workers after <span class="hljs-built_in">this</span> many seconds. Disabled by <span class="hljs-keyword">default</span>.
max-worker-lifetime = <span class="hljs-number">300</span>
</code></pre><p>I am trying to make everything easy with clear explanations. Cross check paths, directory name, and other inputs that you are required to replace.</p>
<p>We need to create the log file and run directory where our socket file will be created, that we just mentioned in our uwsgi.ini:</p>
<pre><code>(project_name)bunny@ip<span class="hljs-number">-172</span><span class="hljs-number">-31</span><span class="hljs-number">-5</span><span class="hljs-number">-231</span>:~$ mkdir log
</code></pre><pre><code>(project_name)bunny@ip<span class="hljs-number">-172</span><span class="hljs-number">-31</span><span class="hljs-number">-5</span><span class="hljs-number">-231</span>:~$ mkdir run
</code></pre><pre><code>(project_name)bunny@ip<span class="hljs-number">-172</span><span class="hljs-number">-31</span><span class="hljs-number">-5</span><span class="hljs-number">-231</span>:~$ touch log/uwsgi.log
</code></pre><p>Make sure to change permissions for these two so that every group or user can write or execute files in these directories:</p>
<pre><code>$ sudo chmod <span class="hljs-number">777</span> /webapps/updateMe/run
</code></pre><pre><code>$ sudo chmod <span class="hljs-number">777</span> /webapps/updateMe/log
</code></pre><p>Now let’s try running the server using <strong>uwsgi.ini</strong> that we just created.</p>
<pre><code>(project_name)bunny@ip<span class="hljs-number">-172</span><span class="hljs-number">-31</span><span class="hljs-number">-5</span><span class="hljs-number">-231</span>:~$ uwsgi --ini /webapps/updateMe/conf/uwsgi.ini
</code></pre><p>If everything up until now is setup correctly, then it should be running. If not, then you need to go back to check for anything you missed (like the path/project name, etc).</p>
<p>To check any uswgi log you can <strong>cat</strong> or <strong>tail</strong> uwsgi.log:</p>
<pre><code>(project_name)bunny@ip<span class="hljs-number">-172</span><span class="hljs-number">-31</span><span class="hljs-number">-5</span><span class="hljs-number">-231</span>:~$ tail log/uwsgi.log
</code></pre><h4 id="heading-create-a-systemd-unit-file-for-uwsgi">Create a systemd Unit File for uWSGI</h4>
<p>At this point if everything is cool, you can even run this command in <a target="_blank" href="http://manpages.ubuntu.com/manpages/bionic/en/man1/screen.1.html">screen</a> and detach it — but again, this is not a good practice at all. Instead we will create a system service and let <strong>systemd</strong> (Ubuntu’s service manager) take care of it.</p>
<h4 id="heading-switch-back-to-sudo-user">Switch back to sudo user</h4>
<pre><code>$ sudo nano /etc/systemd/system/uwsgi.service
</code></pre><p>and copy paste code from the below gist. Don’t forget to update and crosscheck names/path that suit your app:</p>
<pre><code>[Unit]
Description=uWSGI instance to serve updateMe project
After=network.target

[Service]
User=bunny
Group=webapps
WorkingDirectory=<span class="hljs-regexp">/webapps/</span>project_name/src
Environment=<span class="hljs-string">"PATH=/webapps/project_name/bin"</span>
ExecStart=<span class="hljs-regexp">/webapps/</span>project_name/bin/uwsgi --ini /webapps/project_name/conf/uwsgi.ini
Restart=always
KillSignal=SIGQUIT
Type=notify
NotifyAccess=all

[Install]
WantedBy=multi-user.target
</code></pre><p>After you save the above file and close it, you can run following commands:</p>
<p><strong>Reload systemctl daemon to reload systemd manager configuration and recreate the entire dependency tree</strong></p>
<pre><code>$ sudo systemctl daemon-reload
</code></pre><p><strong>Enable uwsgi service to start on system reboot</strong></p>
<pre><code>$ sudo systemctl enable uwsgi
</code></pre><p><strong>Start uwsgi service</strong></p>
<pre><code>$ sudo service uwsgi start
</code></pre><p><strong>Restart uwsgi service</strong></p>
<pre><code>$ sudo service uwsgi restart
</code></pre><p><strong>Check uwsgi service status</strong></p>
<pre><code>$ sudo service uwsgi status
</code></pre><p>Take a deep breath here if everything ran smoothly. We just finished setting up most hectic part of this tutorial, so you should be proud.</p>
<p>Next we will setup NGINX, and then we’ll be done! I know this is taking a bit of time, but believe me — once done, you will be as happy as I will be after publishing this tutorial.</p>
<h3 id="heading-setting-up-nginx-on-ec2-for-uwsgi">Setting Up NGINX on EC2 for uWSGI</h3>
<p>NGINX is a lightweight server, and we’ll use it as a reverse proxy.</p>
<p><strong>We could let uWSGI run directly on port 80, but NGINX has a lot more <a target="_blank" href="https://serverfault.com/questions/590819/why-do-i-need-nginx-when-i-have-uwsgi/590833#590833">benefits</a> which makes it desirable.</strong> Also NGINX natively includes <a target="_blank" href="https://uwsgi-docs.readthedocs.io/en/latest/Nginx.html">support</a> for uWSGI.</p>
<h4 id="heading-enough-talk-lets-install-nginx-on-our-instance">Enough talk, let’s install NGINX on our instance</h4>
<pre><code>$ sudo apt-get install nginx
</code></pre><p>Now when you go to <strong>http://your-public-ip-or-address</strong>, you will see a Nginx welcome page. This is because NGINX is listening to port 80 according to its default configuration.</p>
<p>NGINX has two directories, <strong>sites-available</strong> and <strong>sites-enabled,</strong> that need our attention. <strong>sites-available</strong> stores all conf files for all available sites on that particular instance. <strong>sites-enabled</strong> stores the symbolic link for each enabled site to the sites-available directory.</p>
<p>By default, there is only one conf file named default that has basic setup for NGINX. You can either modify it or create a new one. In our case, I am going to delete it:</p>
<pre><code>$ sudo rm -rf /etc/nginx/sites-available/<span class="hljs-keyword">default</span>
</code></pre><pre><code>$ sudo rm -rf /etc/nginx/sites-enabled/<span class="hljs-keyword">default</span>
</code></pre><p>Let’s create our <strong>nginx-uwsgi.conf</strong> file to connect the browser request to the uwsgi server we are running in site-available:</p>
<pre><code>$ sudo nano /etc/nginx/sites-available/nginx-uwsgi.conf
</code></pre><p>and copy the following code from the gist below:</p>
<pre><code>upstream updateMe_dev {
    server unix:<span class="hljs-regexp">/webapps/u</span>pdateMe/run/uwsgi.sock;
}

server {
    listen <span class="hljs-number">80</span>;
    server_name your-IP-or-address-here;
    charset utf<span class="hljs-number">-8</span>;

    client_max_body_size <span class="hljs-number">128</span>M;

    location /<span class="hljs-keyword">static</span> {
    # exact path to where your <span class="hljs-keyword">static</span> files are located on server 
    # [mostly you won<span class="hljs-string">'t need this, as you will be using some storage service for same]
        alias /webapps/updateMe/static_local;
    }

    location /media {
    # exact path to where your media files are located on server 
    # [mostly you won'</span>t need <span class="hljs-built_in">this</span>, <span class="hljs-keyword">as</span> you will be using some storage service <span class="hljs-keyword">for</span> same]
        alias /webapps/updateMe/media_local;
    }

    location / {
        include uwsgi_params;
        uwsgi_pass updateMe_dev;
        uwsgi_read_timeout <span class="hljs-number">300</span>s;
        uwsgi_send_timeout <span class="hljs-number">300</span>s;
    }

    access_log /webapps/updateMe/log/dev-nginx-access.log;
    error_log /webapps/updateMe/log/dev-nginx-error.log;
}
</code></pre><h4 id="heading-create-symbolic-link-into-sites-enabled-directory-for-same">Create symbolic link into sites-enabled directory for same</h4>
<pre><code>$ sudo ln -s /etc/nginx/sites-available/nginx-uwsgi.conf /etc/nginx/sites-enabled/nginx-uwsgi.conf
</code></pre><p>That’s all, we’re almost there, about to finish up…</p>
<h4 id="heading-reload-systemctl-daemon">Reload systemctl daemon</h4>
<pre><code>$ sudo systemctl daemon-reload
</code></pre><h4 id="heading-enable-nginx-service-on-system-reboot">Enable nginx service on system reboot</h4>
<pre><code>$ sudo systemctl enable nginx
</code></pre><h4 id="heading-start-nginx-service">Start Nginx service</h4>
<pre><code>$ sudo service nginx start
</code></pre><p>Test Nginx. It should return OK, Successful as a part of the result.</p>
<pre><code>$ sudo nginx -t
</code></pre><p>If NGINX fails, you can check its last error-log or access-log on the path specified by us in its conf.</p>
<pre><code>$ tail -f /webapps/updateMe/log/nginx-error.log
</code></pre><pre><code>$ tail -f /webapps/updateMe/log/nginx-access.log
</code></pre><h4 id="heading-restart-nginx-service">Restart Nginx Service</h4>
<pre><code>$ sudo service nginx restart
</code></pre><h4 id="heading-check-nginx-service-status">Check Nginx Service status</h4>
<pre><code>$ sudo service nginx status
</code></pre><p>You should now be able to reach your app at <a target="_blank" href="http://your-public-ip-or-address"><strong>http://your-public-ip-or-address</strong></a></p>
<p>Well this is the end of this lengthy tutorial. I hope you got what you expected from it. Thanks for bearing with me.</p>
<p>PS: uWSGI + NGINX + Django is highly customizable to meet any large scale requirements. That being said, core optimization still lies at application level. How you code and make use of Django ORM or Raw SQL query, etc. will help you further.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to create a VueJS PWA on a high performance, secure NGINX infrastructure ]]>
                </title>
                <description>
                    <![CDATA[ By Thomas Reinecke I’ve been pretty curious ever since one of my developers presented the concept of Progressive Web Apps (PWAs) sometime a few months back to me. And finally I found the time to get my hands a little dirty on it :-) This article desc... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/vuejs-pwa-on-nginx-22360ee7a7bf/</link>
                <guid isPermaLink="false">66c364a2139b845d61e84ba9</guid>
                
                    <category>
                        <![CDATA[ nginx ]]>
                    </category>
                
                    <category>
                        <![CDATA[ General Programming ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Security ]]>
                    </category>
                
                    <category>
                        <![CDATA[ tech  ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Vue.js ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Wed, 08 Aug 2018 11:20:23 +0000</pubDate>
                <media:content url="https://cdn-media-1.freecodecamp.org/images/1*Mbz297hBc_ymcQZWcFP45A.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Thomas Reinecke</p>
<p>I’ve been pretty curious ever since one of my developers presented the concept of <strong>Progressive Web Apps</strong> (PWAs) sometime a few months back to me. And finally I found the time to get my hands a little dirty on it :-)</p>
<p>This article describes my first steps and a high-level intro to PWAs based on the fancy <a target="_blank" href="https://vuejs.org/">VueJS</a> framework, and concentrates primarily on its deployment to a well secured <a target="_blank" href="https://www.nginx.com/">nginx</a> server.</p>
<p>Here are some lectures I used to dive into this topic:</p>
<p><a target="_blank" href="https://developers.google.com/web/progressive-web-apps/"><strong>Progressive Web Apps | Web | Google Developers</strong></a><br><a target="_blank" href="https://developers.google.com/web/progressive-web-apps/">_Lighthouse, an open-source, automated tool for improving the quality of your Progressive Web Apps, eliminates much of…_developers.google.com</a><a target="_blank" href="https://blog.pusher.com/getting-started-pwa-vue/"><strong>Getting started with PWA using Vue - Pusher Blog</strong></a><br><a target="_blank" href="https://blog.pusher.com/getting-started-pwa-vue/">_In this tutorial, have an introduction on how to build a book listing app with progressive web apps using Vue to build…_blog.pusher.com</a></p>
<p>Here is a rough architecture of the scenario we’re going to set up. As you see, this is more a traditional on-Premise setup where I assume you own a domain thats configured and pointed to your root-server. In this article I do not cover cloud-based deployments:</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/T-Dt47h5nkWccQI90G4YG5diB99cLw177VQm" alt="Image" width="800" height="322" loading="lazy">
<em>base architecture</em></p>
<h3 id="heading-warmup-amp-nginx-install">Warmup &amp; nginx install</h3>
<p>Let’s start with some prep-work to setup the infrastructure we will use for deployment later:</p>
<ul>
<li>If you don’t have it yet, get yourself a domain and a root-server and configure your domains DNS records (for IPv4 and IPv6) to point to your server. I’ll use my personal website <a target="_blank" href="http://www.thomas-reinecke.de">www.thomas-reinecke.de</a> as an example here.</li>
<li>On your server, and I am assuming you have root/sudo access, <a target="_blank" href="https://www.digitalocean.com/community/tutorials/how-to-install-nginx-on-ubuntu-16-04">install nginx</a> (example for apt-based linux distributions like debian or ubuntu).</li>
</ul>
<pre><code>sudo apt-get install nginx
</code></pre><ul>
<li>create your server definition on <em>/etc/nginx/sites-available/default</em> which can look pretty basic for now</li>
</ul>
<pre><code>server {    server_name www.thomas-reinecke.de thomas-reinecke.de;    root /server/thomasreinecke/nginx;}
</code></pre><ul>
<li>test your nginx config to make sure its syntax is ok before we move on</li>
</ul>
<pre><code>&gt; nginx -t
</code></pre><pre><code>nginx: the configuration file /etc/nginx/nginx.conf syntax is oknginx: configuration file /etc/nginx/nginx.conf test is successful
</code></pre><h3 id="heading-configure-and-secure-nginx">Configure and secure nginx</h3>
<p>Since our plan is to utilize PWA service workers, your web app has to run on HTTPS (and ideally also on HTTP/2) only. In addition we certainly should support <a target="_blank" href="https://security.googleblog.com/2018/02/a-secure-web-is-here-to-stay.html">Google’s attempts to enforce a more secure web</a>.</p>
<blockquote>
<p>Beginning in July 2018 with the release of Chrome 68, Chrome will mark all HTTP sites as “not secure”.</p>
</blockquote>
<p>Fortunately <a target="_blank" href="https://letsencrypt.org/">Let’s Encrypt</a> provides free SSL certificates for your web server including auto-renewal, and all you need to do is properly secure it.</p>
<ul>
<li>install <strong>certbot</strong> from Let’s Encrypt</li>
</ul>
<pre><code>sudo apt-get install certbot python3-certbot python-certbot-nginx python3-certbot-nginx
</code></pre><ul>
<li>Based on the initial config of your nginx server we’ve done earlier, <strong>certbot</strong> provides a pretty comfortable way to create a certificate for your site including an end-2-end reconfiguration of your nginx service to support SSL and redirect HTTP traffic to HTTPs.</li>
</ul>
<pre><code>sudo certbot --nginx -d thomas-reinecke.de -d www.thomas-reinecke.de
</code></pre><ul>
<li>It’ll ask you whether you want certbot to reconfigure your nginx instance.</li>
</ul>
<pre><code>Please choose whether or not to redirect HTTP traffic to HTTPS, removing HTTP access.-------------------------------------------------------------------<span class="hljs-number">-1</span>: No redirect - Make no further changes to the webserver configuration<span class="hljs-number">.2</span>: Redirect - Make all requests redirect to secure HTTPS access. Choose <span class="hljs-built_in">this</span> fornew sites, or <span class="hljs-keyword">if</span> you<span class="hljs-string">'re confident your site works on HTTPS. You can undo thischange by editing your web server'</span>s configuration.--------------------------------------------------------------------Select the appropriate number [<span class="hljs-number">1</span><span class="hljs-number">-2</span>] then [enter] (press <span class="hljs-string">'c'</span> to cancel):
</code></pre><ul>
<li>Pick the 2nd option and let it go. Expect your site config in <em>/etc/nginx/sites-available/default</em> to look like this afterwards:</li>
</ul>
<pre><code># HTTPS configuration <span class="hljs-keyword">of</span> my siteserver {  server_name thomas-reinecke.de www.thomas-reinecke.de;  root /server/thomasreinecke/nginx;
</code></pre><pre><code>  location / {    index index.html index.htm;  }
</code></pre><pre><code>  listen &lt;your_IP&gt;:<span class="hljs-number">443</span> ssl http2;   ssl_certificate &lt;path_to_fullchain.pem&gt;;   ssl_certificate_key &lt;path_to_privkey.pem&gt;;   include /etc/letsencrypt/options-ssl-nginx.conf;   ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; }
</code></pre><pre><code># HTTP configuration <span class="hljs-keyword">of</span> my site, <span class="hljs-keyword">in</span> any <span class="hljs-keyword">case</span> redirecting to HTTPSserver {  <span class="hljs-keyword">if</span> ($host = www.thomas-reinecke.de) {    <span class="hljs-keyword">return</span> <span class="hljs-number">301</span> https:<span class="hljs-comment">//$host$request_uri;  }</span>
</code></pre><pre><code>  <span class="hljs-keyword">if</span> ($host = thomas-reinecke.de) {    <span class="hljs-keyword">return</span> <span class="hljs-number">301</span> https:<span class="hljs-comment">//$host$request_uri;  }</span>
</code></pre><pre><code>  listen &lt;your_IP&gt;:<span class="hljs-number">80</span> <span class="hljs-keyword">default</span>;  server_name thomas-reinecke.de www.thomas-reinecke.de;  <span class="hljs-keyword">return</span> <span class="hljs-number">404</span>; }
</code></pre><ul>
<li>I added the <strong>http2</strong> option on HTTPs configuration area of your site, this won’t come from certbot.</li>
</ul>
<p>More details about this procedure (including what you’d need to do to renew your certificate) can be found <a target="_blank" href="https://www.digitalocean.com/community/tutorials/how-to-secure-nginx-with-let-s-encrypt-on-ubuntu-16-04">here</a>.</p>
<p>It’s now time to get your nginx server going, so we create a simple index.html and re-bounce nginx to pick it up.</p>
<pre><code>echo <span class="hljs-string">"Hello World!"</span> &gt; <span class="hljs-regexp">/server/</span>thomasreinecke/nginx/index.htmlsudo systemctl reload nginx
</code></pre><p>Congratulations, you should now have your secured webpage online. Any access to HTTP will be auto-redirected to HTTPS. You can now test it on <a target="_blank" href="https://www.ssllabs.com/ssltest/">https://www.ssllabs.com/ssltest/</a> and you’ll get an <strong>A</strong> grade :) How cool is that?</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/2ESrJyGkNj1Y-WYQq3Xp8LTuldlwnrTjuUUn" alt="Image" width="798" height="223" loading="lazy"></p>
<h3 id="heading-create-your-vuejs-pwa-and-deploy-it-to-nginx">Create your vuejs PWA and deploy it to nginx</h3>
<p>Since we have the target deployment infrastructure ready now, it’s time to create your first PWA based on the vuejs CLI.</p>
<pre><code># install vue cli, skip <span class="hljs-keyword">if</span> you already have itnpm install -g vue-cli
</code></pre><pre><code># create vue app based on pwa templatevue init pwa &lt;your_app_name&gt;
</code></pre><p>On the few questions this script presents to you, provide your project’s name and for the rest simply take the defaults. I won’t dive into actual changes of the app that was just created based on the template. Instead let’s concentrate on the deployment and operational aspect to run this on your secured nginx infrastructure. As the next steps, build your app and then move its dist folder into your nginx web root:</p>
<pre><code># build your vuejs pwa appcd &lt;folder <span class="hljs-keyword">of</span> your app&gt;npm run build
</code></pre><pre><code># copy your dist folder onto your server running nginxscp -r dist<span class="hljs-comment">/* &lt;your_server&gt;:/server/thomasreinecke/nginx/</span>
</code></pre><pre><code># on your server restart nginx to refreshsudo systemctl reload nginx
</code></pre><p>You should now have your VueJS PWA running on your nginx server:</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/xCyJM0HpJUk4RVI6UTAqTt6CSzHCBLhdShOf" alt="Image" width="773" height="484" loading="lazy"></p>
<p>When you get to this page with a usual HTTP URL, you’ll notice a redirection to HTTPs, which is perfectly what we wanted. Now it’s time to run <strong>Google Lighthouse</strong> against our site and see how it goes. For more details :</p>
<p><a target="_blank" href="https://developers.google.com/web/tools/lighthouse/"><strong>Lighthouse | Tools for Web Developers | Google Developers</strong></a><br><a target="_blank" href="https://developers.google.com/web/tools/lighthouse/">_Learn how to set up Lighthouse to audit your web apps._developers.google.com</a></p>
<p>Open Chrome &gt; use its Developer Tools &gt; Audits &gt; Run audits. Here is what we get from this setup — isn’t that fantastic !?</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/SD-lwY9PNX3VtjWBxAw3BglL98MrZpaG7CoA" alt="Image" width="728" height="122" loading="lazy"></p>
<h3 id="heading-conclusion">Conclusion</h3>
<p>In this article I showed how to setup a secured environment to run a VueJS-based Progressive Web App on an nginx server. I have to admit, I thought this would be somewhat harder, but the reality was: it took me no longer than ~3hrs in total to get there from zero which was pretty surprising.</p>
<p>Setting up a secured and high-performance infrastructure is really no big deal and I encourage everybody who’s dealing with on-Premise deployments to dive into this.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Powerful ways to supercharge your NGINX server and improve its performance ]]>
                </title>
                <description>
                    <![CDATA[ By HaKr NGINX is perhaps the most versatile web server out there, and it can beat other servers when configured correctly. It can also do other important things, such as load balancing, HTTP caching, and can be used as a reverse proxy. Over the years... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/powerful-ways-to-supercharge-your-nginx-server-and-improve-its-performance-a8afdbfde64d/</link>
                <guid isPermaLink="false">66c35cac258ebfc3dc8f1f70</guid>
                
                    <category>
                        <![CDATA[ Devops ]]>
                    </category>
                
                    <category>
                        <![CDATA[ nginx ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Productivity ]]>
                    </category>
                
                    <category>
                        <![CDATA[ General Programming ]]>
                    </category>
                
                    <category>
                        <![CDATA[ tech  ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Thu, 26 Jul 2018 19:14:54 +0000</pubDate>
                <media:content url="https://cdn-media-1.freecodecamp.org/images/1*ruRaN0YaGlKpZ13eP3dxqA.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By HaKr</p>
<p><a target="_blank" href="http://nginx.org"><strong>NGINX</strong></a> is perhaps the most versatile web server out there, and it can beat other servers when configured correctly. It can also do other important things, such as load balancing, HTTP caching, and can be used as a reverse proxy.</p>
<p>Over the years, we’ve seen so many configurations which improve security and increase the performance of your web application overall — allowing you to keep up with latest trends.</p>
<p>I’m going to share the minimalist NGINX config that I found is most optimised which I have been using for new my new tool <a target="_blank" href="https://visalist.io">VisaList</a>. I had to do a lot of searching and researching for improving the last mile performance of my website, and I thought the process might help at least a few others — so I’m sharing it here.</p>
<h3 id="heading-why">Why?</h3>
<p>With these changes, I was able to get the result below for my new web app:</p>
<p><strong>Page Speed Score:</strong> <a target="_blank" href="https://developers.google.com/speed/pagespeed/insights/">Page Speed Insights</a></p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*japLOsbLszo0zdJIkRcgag.png" alt="Image" width="800" height="401" loading="lazy"></p>
<p><strong>Lighthouse Score:</strong> <a target="_blank" href="https://developers.google.com/web/tools/lighthouse/">Chrome Dev Tools Lighthouse</a></p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*8P0556MeGprSMNTIFk4acQ.jpeg" alt="Image" width="800" height="193" loading="lazy"></p>
<p><strong>Server Score:</strong> <a target="_blank" href="https://www.ssllabs.com/ssltest/index.html">Qualys SSL Server Test</a></p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*Xd1NNM6nhvezzm2-7D7jEQ.png" alt="Image" width="800" height="193" loading="lazy"></p>
<p>You too can get these performance benefits easily. You <strong>don’t</strong> need to be a DevOps expert to make these optimisations. So anyone who is new to web applications and is using NGINX will find this very useful.</p>
<p>If you are an expert, then you could leave your opinions in the comments so the new devs like me can learn and build a strong and positive web community around us. ✨<strong>Go Web Developers!</strong><em>✨</em></p>
<p>This article assumes that you’ve got an Ubuntu 16.04 (Xenial) server and a server rendered WebApp Vue.js (or any other JS framework) application ready to be served through NGINX along with the API server. If you haven't installed NGINX and need help doing that, you can check out this <a target="_blank" href="https://www.digitalocean.com/community/tutorials/how-to-install-nginx-on-ubuntu-16-04"><strong>article</strong></a>.</p>
<p>So what are these optimisations that I’m talking about? Let’s see the code!</p>
<h3 id="heading-optimisations">Optimisations</h3>
<p>The good news is that you only have to bother about two files. One is your overall NGINX config, which applies to all the web apps (you can have multiple web apps like a website, API, static server, and so on). The other is specific to your domain, which, let’s say, is <code>example.com</code>. Replace <code>example.com</code> with your own domain. Here I’m only using the naked domain without <code>www</code>. I will cover that soon.</p>
<p>Open your NGINX config or domain specific config files by using these commands:</p>
<pre><code class="lang-bash">sudo nano /etc/nginx/nginx.conf

sudo nano /etc/nginx/sites-available/example.com
</code></pre>
<h4 id="heading-content-compression"><strong>Content Compression</strong></h4>
<p>Is <strong>Brotli</strong> better than <strong>GZip</strong>? Yes and No. When the browser requests a web page, the server doesn't send it directly byte by byte. Instead, it sends it in a compressed state based on the accepted encodings of the browser. Mostly everyone today uses Gzip, and you may ask why? Because its been around for more than a decade.</p>
<p>So here comes Brotli, which is the latest encoding algorithm developed by Google. Brotli is ~20% more efficient than Gzip. Just keep in mind you should send content in Gzip where Brotli is not supported. Brotli works best with static files rather than dynamic content.</p>
<p>Also make sure you enable the Brotli type for API JSON data only when your client side HTTP library supports it. For example, the Axios library doesn't support Brotli encoding yet.</p>
<pre><code>http {

... 

    # Gzip Settings
    gzip on;
    gzip_disable <span class="hljs-string">"msie6"</span>;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level <span class="hljs-number">6</span>;
    gzip_buffers <span class="hljs-number">32</span> <span class="hljs-number">16</span>k;
    gzip_http_version <span class="hljs-number">1.1</span>;
    gzip_min_length <span class="hljs-number">250</span>;
    gzip_types image/jpeg image/bmp image/svg+xml text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript image/x-icon;

    # Brotli Settings
    brotli on;
    brotli_comp_level <span class="hljs-number">4</span>;
    brotli_buffers <span class="hljs-number">32</span> <span class="hljs-number">8</span>k;
    brotli_min_length <span class="hljs-number">100</span>;
    brotli_static on;
    brotli_types image/jpeg image/bmp image/svg+xml text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript image/x-icon;
...

}
</code></pre><p>Once you add these changes, you can check that the content-encoding is showing <code>br</code> in the response headers in Chrome developer tools:</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*6bM7se1Eqgm4F63n-Q2krg.png" alt="Image" width="582" height="144" loading="lazy"></p>
<h4 id="heading-improve-security"><strong>Improve Security</strong></h4>
<p>By default, your NGINX doesn't have all the important security headers required which is pretty straightforward actually. These prevent clickjacking attacks, cross-site scripting attacks, and other code injection attacks.</p>
<p><code>Strict-Transport-Security</code> header is for <strong>HTTP Strict Transport Security</strong> (HSTS) which also protects from protocol <a target="_blank" href="https://en.wikipedia.org/wiki/Downgrade_attack">downgrade attacks</a>.</p>
<pre><code>http {

...
   # security headers
   add_header X-Frame-Options <span class="hljs-string">"SAMEORIGIN"</span> always;
   add_header X-XSS-Protection <span class="hljs-string">"1; mode=block"</span> always;
   add_header X-Content-Type-Options <span class="hljs-string">"nosniff"</span> always;
   add_header Referrer-Policy <span class="hljs-string">"no-referrer-when-downgrade"</span> always;
   add_header Content-Security-Policy <span class="hljs-string">"default-src * data: 'unsafe-eval' 'unsafe-inline'"</span> always;

   add_header Strict-Transport-Security <span class="hljs-string">"max-age=31536000; includeSubDomains; preload"</span> always;
...

}
</code></pre><h4 id="heading-optimize-ssl-and-sessions"><strong>Optimize SSL and Sessions</strong></h4>
<p>SSL: Use on TLS and disable SSL (SSL is pretty old and outdated and has lot of vulnerabilities ). Optimise cipher suites, as they are the core of TLS. This is where encryption happens.</p>
<p>Session Cache: Creating a cache of TLS connection parameters reduces the number of handshakes, and thus can improve the performance of your application. Caching is configured using <code>ssl_session_cache</code> directive.</p>
<p>Session Tickets: Session tickets are an alternative to session cache. In case of session cache, information about the session is stored on the server.</p>
<p>OSCP: To have a secure connection to a server, the client needs to verify the certificate which the server presented. In order to verify that the certificate is not revoked, the client (browser) will contact the issuer of the certificate. This adds a bit more overhead to connection initialisation (and thus our page load time).</p>
<p>Use these directives in your NGINX config and you are all set for SSL optimisation.</p>
<pre><code>http {

...  
   # SSL Settings
   ssl_protocols TLSv1 TLSv1<span class="hljs-number">.1</span> TLSv1<span class="hljs-number">.2</span>;
   ssl_prefer_server_ciphers on;
   ssl_ciphers ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GC$

   # Optimize session cache
   ssl_session_cache shared:SSL:<span class="hljs-number">50</span>m;
   ssl_session_timeout <span class="hljs-number">1</span>d;

   # Enable session tickets
   ssl_session_tickets on;

   # OCSP Stapling
   ssl_stapling on;
   ssl_stapling_verify on;
   resolver <span class="hljs-number">8.8</span><span class="hljs-number">.8</span><span class="hljs-number">.8</span> <span class="hljs-number">8.8</span><span class="hljs-number">.4</span><span class="hljs-number">.4</span> <span class="hljs-number">208.67</span><span class="hljs-number">.222</span><span class="hljs-number">.222</span> <span class="hljs-number">208.67</span><span class="hljs-number">.220</span><span class="hljs-number">.220</span> valid=<span class="hljs-number">60</span>s;
   resolver_timeout <span class="hljs-number">2</span>s;
...

}
</code></pre><h4 id="heading-improve-performance-http2-support"><strong>Improve Performance: HTTP/2 Support</strong></h4>
<p>HTTP/2 has lot of benefits over HTTP, like allowing the browser to download files in parallel, and allowing the server to push resources, among others. All you have to do is to replace <code>http</code> with <code>http2</code> in your default server block. That’s it, and you get lots and lots of benefits.</p>
<pre><code>server{

...

listen <span class="hljs-number">443</span> http2 default_server;
    listen [::]:<span class="hljs-number">443</span> http2 default_server;
    server_name example.com;
...

}
</code></pre><p>Type this command <code>curl -I -L https://example.com</code> and verify the response.</p>
<pre><code>HTTP/<span class="hljs-number">2</span> <span class="hljs-number">200</span>
<span class="hljs-attr">server</span>: nginx
<span class="hljs-attr">date</span>: Wed, <span class="hljs-number">18</span> Jul <span class="hljs-number">2018</span> <span class="hljs-number">02</span>:<span class="hljs-number">13</span>:<span class="hljs-number">32</span> GMT
content-type: text/html; charset=utf<span class="hljs-number">-8</span>
content-length: <span class="hljs-number">216641</span>
<span class="hljs-attr">vary</span>: Accept-Encoding
....
</code></pre><h4 id="heading-reduce-scrapping-attacks"><strong>Reduce Scrapping / Attacks</strong></h4>
<p>Limiting the requests to the server is critical, as this can easily deplete the resources and can result in huge billings. This is also important to fend off those who want to scrape and attack our servers. There are basically three types of directives:</p>
<ul>
<li>Request Limiting <code>**limit_req**</code> <strong>:</strong> Limit the number of requests per IP</li>
<li>Connections Limiting <code>**limit_conn**</code> <strong>:</strong> Limit the number of Connections per IP</li>
<li>Bandwidth/Rate Limiting <code>**limit_rate**</code> <strong>:</strong> Limit the bandwidth rate of data being sent</li>
</ul>
<p>With the below directive, you can rest easy:</p>
<pre><code>http {

...
   # Limits
   limit_req_log_level warn;
   limit_req_zone $binary_remote_addr zone=reqlimit:<span class="hljs-number">10</span>m rate=<span class="hljs-number">10</span>r/m;

   limit_conn_zone $binary_remote_addr zone=connlimit:<span class="hljs-number">100</span>m;
   limit_conn servers <span class="hljs-number">1000</span>; # Simultaneous Connections
...

}
</code></pre><pre><code>...
server {
...
   location /api/ {
      # Rate Limiting
      limit_req zone=reqlimit burst=<span class="hljs-number">20</span>; # Max burst <span class="hljs-keyword">of</span> request
      limit_req_status <span class="hljs-number">460</span>; # Status to send

      # Connections Limiting
      limit_conn connlimit <span class="hljs-number">20</span>; # <span class="hljs-built_in">Number</span> <span class="hljs-keyword">of</span> downloads per IP       

      # Bandwidth Limiting
      limit_rate <span class="hljs-number">4096</span>k; # Speed limit (here is on kb/s)
   }
...
}
</code></pre><h4 id="heading-client-side-caching"><strong>Client-side Caching</strong></h4>
<p>Caching static files on the browser is easy, and it saves lot of requests to the server. All you have to do is add these two code blocks and specify the expiration as you please. You can include any other static file extension you deem worthy of caching.</p>
<pre><code>server {

...
    location / {
       ...
       ...
    }
    ...
    ...
    location ~* \.(jpg|jpeg|png|gif|ico)$ {
       expires <span class="hljs-number">30</span>d;
    }
    location ~* \.(css|js)$ {
       expires <span class="hljs-number">7</span>d;
    }
...

}
</code></pre><h4 id="heading-microcaching"><strong>Microcaching</strong></h4>
<p>If you haven’t heard about this until now, then you are in luck today! <strong>Microcaching</strong> is a caching technique in which content is cached for a very short period of time, perhaps as little as 1 second. This effectively means that updates to the site are delayed by no more than a second, which in many cases is perfectly acceptable. This is particularly useful for API responses which are the same for all users.</p>
<p>Use these directives to set microcaching with the path at <code>/tmp/cacheapi</code> with 100MB cache with a max size of 1GB of cache folder that updates cache in the background. Learn more about it <a target="_blank" href="https://www.nginx.com/blog/benefits-of-microcaching-nginx/"><strong>here</strong></a> and <a target="_blank" href="https://www.nginx.com/blog/nginx-caching-guide/"><strong>here</strong></a>.</p>
<pre><code>proxy_cache_path /tmp/cacheapi levels=<span class="hljs-number">1</span>:<span class="hljs-number">2</span> keys_zone=microcacheapi:<span class="hljs-number">100</span>m max_size=<span class="hljs-number">1</span>g inactive=<span class="hljs-number">1</span>d use_temp_path=off;
...

server {

...
   location /api/ {

      # Micro caching
      proxy_cache microcacheapi;
      proxy_cache_valid <span class="hljs-number">200</span> <span class="hljs-number">1</span>s;
      proxy_cache_use_stale updating;
      proxy_cache_background_update on;
      proxy_cache_lock on;
      ...
      ...

   }
...

}
</code></pre><pre><code>http {

...
   add_header X-Cache-Status $upstream_cache_status;
...

}
</code></pre><h4 id="heading-ssl-certificate"><strong>SSL Certificate</strong></h4>
<p><strong>Let’s Encrypt</strong> is a Certificate Authority (CA) that provides an easy way to obtain and install free TLS/SSL certificates. This enables encrypted HTTPS on web servers. It simplifies the process by providing a software client, Certbot, that attempts to automate most (if not all) of the required steps.</p>
<p>Install <a target="_blank" href="https://letsencrypt.org">LetsEnctypt</a>:</p>
<pre><code class="lang-bash">sudo add-apt-repository ppa:certbot/certbot
sudo apt-get update
sudo apt-get install python-certbot-nginx
</code></pre>
<p>Create a LetsEncrypt SSL certificate with this command:</p>
<pre><code class="lang-bash">sudo certbot --nginx -d example.com -d www.example.com
</code></pre>
<p>and then add these certificates to your domain config file like this:</p>
<pre><code>server {

    listen <span class="hljs-number">443</span> ssl http2 default_server;
    listen [::]:<span class="hljs-number">443</span> ssl http2 default_server;
...
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
...

}
</code></pre><h4 id="heading-redirect-www"><strong>Redirect WWW</strong></h4>
<p>Google prefers that you choose a domain with <code>www</code> instead of without. It’s better to choose the naked domain as its smaller and removes the redundant <code>www</code> . You can now redirect all <code>www</code> users to your naked domain by adding these below directives.</p>
<pre><code>server {
...
...
}
server {
    listen <span class="hljs-number">80</span>;
    listen [::]:<span class="hljs-number">80</span>;
    server_name example.com;
    <span class="hljs-keyword">return</span> <span class="hljs-number">301</span> https:<span class="hljs-comment">//$server_name$request_uri;</span>
}
server {
    listen <span class="hljs-number">80</span>;
    listen [::]:<span class="hljs-number">80</span>;
    listen <span class="hljs-number">443</span> ssl http2;
    listen [::]:<span class="hljs-number">443</span> ssl http2;
    server_name www.example.com;
    <span class="hljs-keyword">return</span> <span class="hljs-number">301</span> https:<span class="hljs-comment">//example.com$request_uri;</span>
}
</code></pre><h4 id="heading-pagespeed-module"><strong>Pagespeed Module</strong></h4>
<p><a target="_blank" href="https://www.modpagespeed.com">Pagespeed Module</a> is a gem unknown to many. It was originally a Google Project which is now part of Apache Incubator. Pagespeed can automatically take care of almost all the known ways to improve performance on your website.</p>
<p><a target="_blank" href="https://www.digitalocean.com/community/tutorials/how-to-add-ngx_pagespeed-to-nginx-on-ubuntu-14-04"><strong>Install</strong></a> or <a target="_blank" href="https://www.digitalocean.com/community/questions/how-to-add-pagespeed-on-existent-nginx-ubuntu-server">Upgrade</a> NGINX with Pagespeed. This is not an easy task, and that’s why I have saved it for last. Follow these instructions, and you should be able to do it without any hastle. Once you’re done, all you need to do is enable it and voilà!</p>
<pre><code>server{

...  
    # Pagespeed Module
    pagespeed on;

    pagespeed FileCachePath /<span class="hljs-keyword">var</span>/cache/ngx_pagespeed_cache;
    location ~ <span class="hljs-string">"\.pagespeed\.([a-z]\.)?[a-z]{2}\.[^.]{10}\.[^.]+"</span> {
    add_header <span class="hljs-string">""</span> <span class="hljs-string">""</span>;
    }
    location ~ <span class="hljs-string">"^/pagespeed_static/"</span> { }
    location ~ <span class="hljs-string">"^/ngx_pagespeed_beacon$"</span> { }

    pagespeed RewriteLevel PassThrough;
    pagespeed EnableCachePurge on;
    pagespeed PurgeMethod PURGE;

    pagespeed EnableFilters prioritize_critical_css;
...
}
</code></pre><p>There are so many filters that you can enable, but just keep in mind that most of the modern frameworks like (Nuxt.js, Angular, Next.js and so on) have some of these optimisations as part of their build process, so this can be counterintuitive. Choose filters which you need and enable only them. This is not by any means an exhaustive set of filters, but would definitely take your site to 100/100 on pagespeed.</p>
<pre><code>pagespeed EnableFilters rewrite_css;
pagespeed EnableFilters collapse_whitespace,remove_comments;
pagespeed EnableFilters flatten_css_imports;
pagespeed EnableFilters combine_css;
pagespeed EnableFilters combine_javascript;
pagespeed EnableFilters defer_javascript;
pagespeed EnableFilters extend_cache;
pagespeed EnableFilters pedantic;
pagespeed EnableFilters inline_google_font_css;
pagespeed FetchHttps enable;
pagespeed EnableFilters inline_css,move_css_above_scripts;
pagespeed EnableFilters inline_javascript;
pagespeed EnableFilters inline_import_to_link;
pagespeed EnableFilters lazyload_images;
pagespeed EnableFilters insert_dns_prefetch;
pagespeed EnableFilters inline_preview_images;
pagespeed EnableFilters resize_mobile_images;
pagespeed EnableFilters rewrite_images;
pagespeed EnableFilters responsive_images,resize_images;
pagespeed EnableFilters responsive_images_zoom;
pagespeed EnableFilters rewrite_javascript;
pagespeed EnableFilters rewrite_style_attributes,convert_meta_tags;
</code></pre><p>You can read more about different types of filters available <a target="_blank" href="https://modpagespeed.com/doc/filters"><strong>here</strong></a><strong>.</strong></p>
<div class="embed-wrapper"><iframe src="https://giphy.com/embed/ely3apij36BJhoZ234" width="480" height="480" class="giphy-embed" title="Embedded content" loading="lazy"></iframe></div>

<p>So the final NGINX config and domain config looks something like this:</p>
<p><a target="_blank" href="https://gist.github.com/1hakr/01cb00dfce8c92a15c0d9faee9052042">https://gist.github.com/1hakr/01cb00dfce8c92a15c0d9faee9052042</a></p>
<p>Now all you have to do is reload your NGINX config file by typing the below commands and you have supercharged your NGINX server:</p>
<pre><code>sudo nginx -t

sudo systemctl restart nginx
</code></pre><p>Pro Tip: If you find this article beyond your reach, then there is a simple website which can get the final config file for you: check out <a target="_blank" href="https://nginxconfig.io/"><strong>NGINX Config</strong></a><strong>.</strong></p>
<p>I hope you like this NGINX config and are able to supercharge your web apps. Do you already use something similar or have a different opinion altogether? Let me know in the comments.</p>
<p>This is my new microstartup, <a target="_blank" href="https://visalist.io">VisaList</a>, where I have applied these optimisations. It can help you find visa requirements for all countries in the world in a simple and useful way.</p>
<p><a target="_blank" href="https://visalist.io"><strong>Find countries to visit across the world</strong></a><br><a target="_blank" href="https://visalist.io"><em>Search from list of countries your can travel with Visa free, Visa on arrival and other requirements from more than…</em></a><br><a target="_blank" href="https://visalist.io">visalist.io</a></p>
<p>That all folks! This is <a target="_blank" href="https://1hakr.com"><strong>HaKr</strong></a> signing off. Thanks for reading, and if you found this useful do click ? to recommend this article to others so they can find it, too.</p>
<p>I build <a target="_blank" href="https://dworks.io"><strong>microstartups</strong></a> while travelling when I can. If you find this kind of stuff interesting, you can follow me on <a target="_blank" href="https://twitter.com/1hakr">Twitter</a> and check out my open-source work on <a target="_blank" href="https://github.com/1hakr">GitHub</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
