commit - 6f5121b2fe9bd31eb2b4affbaca4102476941251
commit + 6e18b1ee81a6023d30bc59fa19e2aeba2202028a
blob - de4723502052d2d2ada53fd2fe14582901ea7ed0
blob + a57283d627820e6bdd9f9ec44d3208d95e73d0f9
--- _header.html
+++ _header.html
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>httpd.rocks</title>
+ <title>httpd.rocks - Setup an HTTPS-enabled webserver with httpd on OpenBSD</title>
<link rel="stylesheet" href="./style.css">
</head>
<body>
blob - 9479a182ccc48373cc830413a3dd6560e0087018
blob + ff08ab3e43b81d011ab7d64c630647e678169258
--- index.html
+++ index.html
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>httpd.rocks</title>
+ <title>httpd.rocks - Setup an HTTPS-enabled webserver with httpd on OpenBSD</title>
<link rel="stylesheet" href="./style.css">
</head>
<body>
<p><img src="openbsd-logo.svg" alt="OpenBSD mascot" /></p>
<h1 id="httpd-rocks">httpd rocks</h1>
-<p>A barebones guide to setup an HTTPS-enabled web server with <code>httpd</code> on <a href="https://openbsd.org">OpenBSD</a></p>
-<p>Help improve this website: <a href="">git.btxx.org/httpd.rocks</a></p>
+<p>Setup an <a href="https://www.ssllabs.com/ssltest/analyze.html?d=httpd.rocks">HTTPS-enabled web server</a> with <code>httpd</code> on <a href="https://openbsd.org">OpenBSD</a></p>
+<p>Includes <a href="https://securityheaders.com/?q=https%3A%2F%2Fhttpd.rocks%2F&followRedirects=on">A+ security report</a> configuration with <code>haproxy</code>.</p>
+<blockquote>
+<p>I’m far from an expert! Please <a href="https://git.btxx.org/httpd.rocks">help improve this project</a></p>
+</blockquote>
<hr/>
<h2 id="before-you-begin">Before You Begin…</h2>
<p>This guide assumes you have already setup OpenBSD on your desired server of choice. Most commands will need to run via <code>doas</code>, since you should be logged in as a created user - <strong>never</strong> <code>root</code> directly.</p>
</code></pre>
<h2 id="section"><code>pf.conf</code></h2>
<p>Before doing anything else, you need to make sure your <code>/etc/pf.conf</code> is allowing traffic on ports <code>80</code> and <code>443</code>. Make sure you include the following:</p>
-<pre><code>pass in on egress proto tcp from any to any port 80
-pass in on egress proto tcp from any to any port 443
+<pre><code>pass in on any proto { tcp, udp } from any to any port 53
+pass out on any proto { tcp, udp } from any to any port 53
-pass out on egress from any to any
+pass in on any proto tcp from any to any port {80 443}
+pass out on any proto tcp from any to any port {80 443}
</code></pre>
<h2 id="section-1"><code>httpd.conf</code></h2>
<p>Make initial website folder and files:</p>
request strip 2
}
}
-
-server "www.httpd.rocks" {
- listen on * port 80
- block return 301 "http://httpd.rocks$REQUEST_URI"
-}
</code></pre>
-<p>We need to create proper directories for <code>acme-client</code> (our next steps) and set their permissions:</p>
-<pre><code>doas mkdir -p -m 750 /etc/ssl/private
-doas mkdir -p -m 755 /var/www/acme
-</code></pre>
<p>Then get <code>httpd</code> up and running:</p>
<pre><code>doas rcctl start httpd
</code></pre>
<p><strong>Note</strong>: If you encounter runtime errors with <code>httpd</code>, you might be required to add the following to your <code>/etc/rc.conf.local</code> file:</p>
<pre><code>httpd_flags=""
</code></pre>
-<p>If everything was setup properly, you should be able to visit the HTTP-only version of your website online.</p>
+<p>If everything was setup properly, you should be able to visit the HTTP-only version of your website online. The only problem is HTTPS isn’t setup…</p>
+<p>..yet!</p>
<h2 id="section-2"><code>acme-client.conf</code></h2>
+<p>Before anything else, we need to create proper directories for <code>acme-client</code> (our next steps) and set their permissions:</p>
+<pre><code>doas mkdir -p -m 750 /etc/ssl/private
+doas mkdir -p -m 755 /var/www/acme
+</code></pre>
<p>Create the <code>/etc/acme-client.conf</code> file and include the following:</p>
<pre><code>authority letsencrypt {
api url "https://acme-v02.api.letsencrypt.org/directory"
account key "/etc/acme/letsencrypt-privkey.pem"
}
+
domain httpd.rocks {
- alternative names { www.httpd.rocks }
domain key "/etc/ssl/private/httpd.rocks.key"
- domain certificate "/etc/ssl/private/httpd.rocks.crt"
+ domain full chain certificate "/etc/ssl/httpd.rocks.fullchain.pem"
sign with letsencrypt
}
</code></pre>
-<p><strong>Note</strong>: The <code>alternative names { www.httpd.rocks }</code> will be needed later to forward all <code>www.</code> requests to standard <code>https://</code>.</p>
<p>Now we can run the core <code>acme-client</code> command to generate our certificates:</p>
<pre><code>doas acme-client -v httpd.rocks
</code></pre>
-<p>If everything goes smoothly, your new certificates should be generated and issued. The next thing you will want to do is automatically check for expired certs. Setup the following <code>cronjob</code> by running <code>crontab -e</code> and entering in:</p>
-<pre><code>0 0 * * * acme-client httpd.rocks && rcctl reload httpd
+<p>If everything goes smoothly, your new certificates should be generated and issued. The next thing you will want to do is automatically check for expired certs. </p>
+<p>First create a separate script (this will be helpful if you plan to host multiple sites on a single server). Name it something like <code>renew_certs.sh</code> and save it under a local directory (ie. <code>/home/username/scripts</code>):</p>
+<pre><code>#!/bin/sh
+doas acme-client httpd.rocks || exit 1
+doas acme-client example.com || exit 1
+doas acme-client anotherdomain.com || exit 1
+doas rcctl reload httpd
</code></pre>
+<blockquote>
+<p>For reference I have included multiple domains if you decide to host several websites through one server. Remove these if you only plan to host a single domain.</p>
+</blockquote>
+<p>Set executable permissions:</p>
+<pre><code>doas chmod +x /path/to/renew_certs.sh
+</code></pre>
+<p>Then setup the following <code>cronjob</code> by running <code>crontab -e</code> and entering in:</p>
+<pre><code>0 0 * * * /path/to/renew_certs.sh
+</code></pre>
<p>This will check if you need to renew certificates every day at midnight (server time).</p>
-<h2 id="again"><code>httpd.conf</code> (again)</h2>
-<p>Now we alter our existing <code>/etc/httpd.conf</code> to properly setup HTTPS and forward all HTTP traffic:</p>
-<pre><code>server "httpd.rocks" {
- listen on * tls port 443
- root "/htdocs/httpd.rocks"
- tls {
- certificate "/etc/ssl/private/httpd.rocks.crt"
- key "/etc/ssl/private/httpd.rocks.key"
- }
-}
+<h2 id="section-3"><code>haproxy.cfg</code></h2>
+<p>Many people tend to reach for <code>relayd</code> in OpenBSD when deciding to setup proxies or include security headers for their sites. Maybe I’m just too dull, but I always struggle to get things running smoothly with it.</p>
+<p>That’s why I opt for using HAProxy.</p>
+<p>First, install the package:</p>
+<pre><code>doas pkg_add haproxy
+</code></pre>
+<p>Now configure the core <code>/etc/haproxy/haproxy.cfg</code> (take note of the extension! OpenBSD uses <code>cfg</code> rather than the standard <code>conf</code> for HAProxy) and add the following to the existing file:</p>
+<pre><code>defaults
+ log global
+ mode http
+ option httplog
+ option dontlognull
+ timeout connect 5000ms
+ timeout client 50000ms
+ timeout server 50000ms
-server "www.httpd.rocks" {
- listen on * tls port 443
- tls {
- certificate "/etc/ssl/private/httpd.rocks.crt"
- key "/etc/ssl/private/httpd.rocks.key"
- }
- block return 301 "https://httpd.rocks$REQUEST_URI"
-}
+frontend http_in
+ bind *:80
+ # Redirect all www requests to non-www
+ http-request redirect prefix https://%[hdr(host),regsub(^www\.,)] code 301 if { hdr_beg(host) -i www }
+ redirect scheme https if !{ ssl_fc }
+ default_backend main_backend
-server "httpd.rocks" {
- listen on * port 80
- location "/.well-known/acme-challenge/*" {
- root "/acme"
- request strip 2
- }
- block return 301 "https://httpd.rocks$REQUEST_URI"
-}
+frontend https_in
+ bind *:443 ssl crt /etc/ssl/certs/
+ # Redirect all www requests to non-www
+ http-request redirect prefix https://%[hdr(host),regsub(^www\.,)] code 301 if { hdr_beg(host) -i www }
+ default_backend main_backend
-server "www.httpd.rocks" {
- listen on * port 80
- block return 301 "http://httpd.rocks$REQUEST_URI"
-}
+# Backend to httpd with security headers, no TLS
+backend main_backend
+ http-response set-header Strict-Transport-Security "max-age=31536000; includeSubDomains"
+ http-response set-header X-Content-Type-Options "nosniff"
+ http-response set-header X-Frame-Options "DENY"
+ http-response set-header X-XSS-Protection "1; mode=block"
+ http-response set-header Content-Security-Policy "script-src 'self'"
+ http-response set-header Referrer-Policy "no-referrer"
+ http-response set-header Permissions-Policy "microphone=()"
+ server local_httpd 127.0.0.1:8080
</code></pre>
-<p>Then test and restart <code>httpd</code>:</p>
-<pre><code>httpd -n
-doas rcctl restart httpd
+<p>The <code>haproxy.cfg</code> in a nutshell:</p>
+<ul>
+<li><code>frontend http_in</code>:
+<ul>
+<li>redirects all <code>www</code> requests to <code>non-www</code></li>
+<li>redirects all HTTP requests to HTTPS</li>
+<li>uses <code>main_backend</code> to set security headers</li>
+</ul></li>
+<li><code>frontend https_in</code>:
+<ul>
+<li>redirects all <code>www</code> requests to <code>non-www</code></li>
+<li>uses <code>main_backend</code> to set security headers</li>
+</ul></li>
+<li><code>backend main_backend</code>:
+<ul>
+<li>sets sane security header defaults (change these as you please)</li>
+</ul></li>
+</ul>
+<p><strong>Important</strong>: Take note of the line:</p>
+<pre><code>bind *:443 ssl crt /etc/ssl/certs/
</code></pre>
-<p>Be sure to also have <code>httpd</code> start on boot (in case of accidental server restarts!):</p>
-<pre><code>doas rcctl enable httpd
+<p>This tells HAProxy to dynamically scan a directory containing our certificates. This is handy if you decide to host multiple sites on a single server. Otherwise, you would have to edit and reload your HAProxy config everytime you setup a new website.</p>
+<p>So we need to create our <code>certs</code> directory:</p>
+<pre><code>doas mkdir /etc/ssl/certs
+doas chmod 644 /etc/ssl/certs
</code></pre>
+<p>And then create <code>/etc/ssl/certs/httpd.rocks.pem</code> by combining your certificate and private key if not done:</p>
+<pre><code>cat /etc/ssl/httpd.rocks.crt /etc/ssl/private/httpd.rocks.key > /etc/ssl/certs/httpd.rocks.pem
+</code></pre>
+<p>Once that’s complete, test that everything is working, and if so, enable and start HAProxy:</p>
+<pre><code>doas haproxy -c -f /etc/haproxy/haproxy.cfg
+doas rcctl enable haproxy
+doas rcctl start haproxy
+</code></pre>
<h2 id="its-alive">It’s Alive!</h2>
-<p>Now check out your website! Everything should work as intended. You should have valid TLS and your standard HTTP request should forward to HTTPS.</p>
+<p>Now check out your website! Everything should work as intended. You should have valid TLS, your standard HTTP request should forward to HTTPS, <code>www</code> requests should forward to <code>non-www</code>, and your security headers should <a href="https://securityheaders.com/?q=https%3A%2F%2Fhttpd.rocks%2F&followRedirects=on">score an A+</a>.</p>
<p>That’s it!</p>
<hr/>
<h2 id="references">References</h2>
blob - 0e9842a5db2596281fa3b721edaea066efbf06be
blob + d0de539fbc8dc73232278b49b571f175f83c1622
--- index.md
+++ index.md
# httpd rocks
-A barebones guide to setup an HTTPS-enabled web server with `httpd` on [OpenBSD](https://openbsd.org)
+Setup an [HTTPS-enabled web server](https://www.ssllabs.com/ssltest/analyze.html?d=httpd.rocks) with `httpd` on [OpenBSD](https://openbsd.org)
-Help improve this website: [git.btxx.org/httpd.rocks]()
+Includes [A+ security report](https://securityheaders.com/?q=https%3A%2F%2Fhttpd.rocks%2F&followRedirects=on) configuration with `haproxy`.
+> I'm far from an expert! Please [help improve this project](https://git.btxx.org/httpd.rocks)
+
---
## Before You Begin...
Before doing anything else, you need to make sure your `/etc/pf.conf` is allowing traffic on ports `80` and `443`. Make sure you include the following:
```
-pass in on egress proto tcp from any to any port 80
-pass in on egress proto tcp from any to any port 443
+pass in on any proto { tcp, udp } from any to any port 53
+pass out on any proto { tcp, udp } from any to any port 53
-pass out on egress from any to any
+pass in on any proto tcp from any to any port {80 443}
+pass out on any proto tcp from any to any port {80 443}
```
## `httpd.conf`
request strip 2
}
}
-
-server "www.httpd.rocks" {
- listen on * port 80
- block return 301 "http://httpd.rocks$REQUEST_URI"
-}
```
-We need to create proper directories for `acme-client` (our next steps) and set their permissions:
-
-```
-doas mkdir -p -m 750 /etc/ssl/private
-doas mkdir -p -m 755 /var/www/acme
-```
-
Then get `httpd` up and running:
```
httpd_flags=""
```
-If everything was setup properly, you should be able to visit the HTTP-only version of your website online.
+If everything was setup properly, you should be able to visit the HTTP-only version of your website online. The only problem is HTTPS isn't setup...
+..yet!
+
## `acme-client.conf`
+Before anything else, we need to create proper directories for `acme-client` (our next steps) and set their permissions:
+
+```
+doas mkdir -p -m 750 /etc/ssl/private
+doas mkdir -p -m 755 /var/www/acme
+```
+
Create the `/etc/acme-client.conf` file and include the following:
```
api url "https://acme-v02.api.letsencrypt.org/directory"
account key "/etc/acme/letsencrypt-privkey.pem"
}
+
domain httpd.rocks {
- alternative names { www.httpd.rocks }
domain key "/etc/ssl/private/httpd.rocks.key"
- domain certificate "/etc/ssl/private/httpd.rocks.crt"
+ domain full chain certificate "/etc/ssl/httpd.rocks.fullchain.pem"
sign with letsencrypt
}
```
-**Note**: The `alternative names { www.httpd.rocks }` will be needed later to forward all `www.` requests to standard `https://`.
-
Now we can run the core `acme-client` command to generate our certificates:
```
doas acme-client -v httpd.rocks
```
-If everything goes smoothly, your new certificates should be generated and issued. The next thing you will want to do is automatically check for expired certs. Setup the following `cronjob` by running `crontab -e` and entering in:
+If everything goes smoothly, your new certificates should be generated and issued. The next thing you will want to do is automatically check for expired certs.
+First create a separate script (this will be helpful if you plan to host multiple sites on a single server). Name it something like `renew_certs.sh` and save it under a local directory (ie. `/home/username/scripts`):
+
```
-0 0 * * * acme-client httpd.rocks && rcctl reload httpd
+#!/bin/sh
+doas acme-client httpd.rocks || exit 1
+doas acme-client example.com || exit 1
+doas acme-client anotherdomain.com || exit 1
+doas rcctl reload httpd
```
+> For reference I have included multiple domains if you decide to host several websites through one server. Remove these if you only plan to host a single domain.
+
+Set executable permissions:
+
+```
+doas chmod +x /path/to/renew_certs.sh
+```
+
+Then setup the following `cronjob` by running `crontab -e` and entering in:
+
+```
+0 0 * * * /path/to/renew_certs.sh
+```
+
This will check if you need to renew certificates every day at midnight (server time).
-## `httpd.conf` (again)
+## `haproxy.cfg`
-Now we alter our existing `/etc/httpd.conf` to properly setup HTTPS and forward all HTTP traffic:
+Many people tend to reach for `relayd` in OpenBSD when deciding to setup proxies or include security headers for their sites. Maybe I'm just too dull, but I always struggle to get things running smoothly with it.
+That's why I opt for using HAProxy.
+
+First, install the package:
+
```
-server "httpd.rocks" {
- listen on * tls port 443
- root "/htdocs/httpd.rocks"
- tls {
- certificate "/etc/ssl/private/httpd.rocks.crt"
- key "/etc/ssl/private/httpd.rocks.key"
- }
-}
+doas pkg_add haproxy
+```
-server "www.httpd.rocks" {
- listen on * tls port 443
- tls {
- certificate "/etc/ssl/private/httpd.rocks.crt"
- key "/etc/ssl/private/httpd.rocks.key"
- }
- block return 301 "https://httpd.rocks$REQUEST_URI"
-}
+Now configure the core `/etc/haproxy/haproxy.cfg` (take note of the extension! OpenBSD uses `cfg` rather than the standard `conf` for HAProxy) and add the following to the existing file:
-server "httpd.rocks" {
- listen on * port 80
- location "/.well-known/acme-challenge/*" {
- root "/acme"
- request strip 2
- }
- block return 301 "https://httpd.rocks$REQUEST_URI"
-}
+```
+defaults
+ log global
+ mode http
+ option httplog
+ option dontlognull
+ timeout connect 5000ms
+ timeout client 50000ms
+ timeout server 50000ms
-server "www.httpd.rocks" {
- listen on * port 80
- block return 301 "http://httpd.rocks$REQUEST_URI"
-}
+frontend http_in
+ bind *:80
+ # Redirect all www requests to non-www
+ http-request redirect prefix https://%[hdr(host),regsub(^www\.,)] code 301 if { hdr_beg(host) -i www }
+ redirect scheme https if !{ ssl_fc }
+ default_backend main_backend
+
+frontend https_in
+ bind *:443 ssl crt /etc/ssl/certs/
+ # Redirect all www requests to non-www
+ http-request redirect prefix https://%[hdr(host),regsub(^www\.,)] code 301 if { hdr_beg(host) -i www }
+ default_backend main_backend
+
+# Backend to httpd with security headers, no TLS
+backend main_backend
+ http-response set-header Strict-Transport-Security "max-age=31536000; includeSubDomains"
+ http-response set-header X-Content-Type-Options "nosniff"
+ http-response set-header X-Frame-Options "DENY"
+ http-response set-header X-XSS-Protection "1; mode=block"
+ http-response set-header Content-Security-Policy "script-src 'self'"
+ http-response set-header Referrer-Policy "no-referrer"
+ http-response set-header Permissions-Policy "microphone=()"
+ server local_httpd 127.0.0.1:8080
```
-Then test and restart `httpd`:
+The `haproxy.cfg` in a nutshell:
+- `frontend http_in`:
+ - redirects all `www` requests to `non-www`
+ - redirects all HTTP requests to HTTPS
+ - uses `main_backend` to set security headers
+
+- `frontend https_in`:
+ - redirects all `www` requests to `non-www`
+ - uses `main_backend` to set security headers
+
+- `backend main_backend`:
+ - sets sane security header defaults (change these as you please)
+
+**Important**: Take note of the line:
+
```
-httpd -n
-doas rcctl restart httpd
+bind *:443 ssl crt /etc/ssl/certs/
```
-Be sure to also have `httpd` start on boot (in case of accidental server restarts!):
+This tells HAProxy to dynamically scan a directory containing our certificates. This is handy if you decide to host multiple sites on a single server. Otherwise, you would have to edit and reload your HAProxy config everytime you setup a new website.
+So we need to create our `certs` directory:
+
```
-doas rcctl enable httpd
+doas mkdir /etc/ssl/certs
+doas chmod 644 /etc/ssl/certs
```
+And then create `/etc/ssl/certs/httpd.rocks.pem` by combining your certificate and private key if not done:
+
+```
+cat /etc/ssl/httpd.rocks.crt /etc/ssl/private/httpd.rocks.key > /etc/ssl/certs/httpd.rocks.pem
+```
+
+Once that's complete, test that everything is working, and if so, enable and start HAProxy:
+
+```
+doas haproxy -c -f /etc/haproxy/haproxy.cfg
+doas rcctl enable haproxy
+doas rcctl start haproxy
+```
+
## It's Alive!
-Now check out your website! Everything should work as intended. You should have valid TLS and your standard HTTP request should forward to HTTPS.
+Now check out your website! Everything should work as intended. You should have valid TLS, your standard HTTP request should forward to HTTPS, `www` requests should forward to `non-www`, and your security headers should [score an A+](https://securityheaders.com/?q=https%3A%2F%2Fhttpd.rocks%2F&followRedirects=on).
That's it!
blob - 90dd4eb49ec91615229acfabacd5a46e220645c9
blob + 6695cce0e3a8f38262b55914f2944e566243e17c
--- style.css
+++ style.css
margin: 2rem 0 0;
}
+blockquote {
+ background: rgba(0,0,0,0.04);
+ border-left: 4px solid;
+ margin: 2rem 0;
+ padding: 6px 10px;
+}
+blockquote p {
+ margin: 0;
+}
+
pre {
border: 1px solid;
+ overflow: scroll;
padding: 10px;
+ max-width:100%;
}
hr {