{"id":28483252,"url":"https://github.com/paraphraser/ssl-certificates","last_synced_at":"2026-04-28T12:38:52.491Z","repository":{"id":262030124,"uuid":"875219029","full_name":"Paraphraser/ssl-certificates","owner":"Paraphraser","description":"Creating private self-signed SSL certificates","archived":false,"fork":false,"pushed_at":"2025-05-27T03:26:28.000Z","size":19364,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-04-17T06:35:24.815Z","etag":null,"topics":["bash-script","debian","domain-certificate","ios","macos","nginx-docker","nginx-proxy","openssl","private-certificate-authority","proxmox-ve","reverse-proxy","server-certificates","ubuntu","wildcard-certificates"],"latest_commit_sha":null,"homepage":"","language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Paraphraser.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-10-19T12:04:55.000Z","updated_at":"2026-02-17T09:05:03.000Z","dependencies_parsed_at":"2025-05-27T04:44:16.592Z","dependency_job_id":null,"html_url":"https://github.com/Paraphraser/ssl-certificates","commit_stats":{"total_commits":5,"total_committers":1,"mean_commits":5.0,"dds":0.0,"last_synced_commit":"a3a6542bb962658dc0c8d3d7e64983ee99533918"},"previous_names":["paraphraser/proxmox-private-ssl","paraphraser/ssl-certificates"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/Paraphraser/ssl-certificates","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Paraphraser%2Fssl-certificates","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Paraphraser%2Fssl-certificates/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Paraphraser%2Fssl-certificates/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Paraphraser%2Fssl-certificates/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Paraphraser","download_url":"https://codeload.github.com/Paraphraser/ssl-certificates/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Paraphraser%2Fssl-certificates/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32381691,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-28T11:25:28.583Z","status":"ssl_error","status_checked_at":"2026-04-28T11:25:05.435Z","response_time":56,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bash-script","debian","domain-certificate","ios","macos","nginx-docker","nginx-proxy","openssl","private-certificate-authority","proxmox-ve","reverse-proxy","server-certificates","ubuntu","wildcard-certificates"],"created_at":"2025-06-07T21:38:06.100Z","updated_at":"2026-04-28T12:38:52.462Z","avatar_url":"https://github.com/Paraphraser.png","language":"Shell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Private SSL certificates\n\n## scenario\n\nYou have installed an [HTTPS](https://datatracker.ietf.org/doc/html/rfc2818)-based service on your home network. You have connected to it with your browser. And you are staring at:\n\n![This connection is not private](./images/not-private.png)\n\nYou may have been clicking \u003ckbd\u003eShow Details\u003c/kbd\u003e and visiting the site anyway but, eventually, the alert starts to become *really* annoying.\n\nYou Google the topic and search YouTube. If your experience is anything like mine, you'll find a lot of information about registering a domain, then using [Let's Encrypt](https://letsencrypt.org) and the [ACME protocol](https://datatracker.ietf.org/doc/html/rfc8555). There is nothing wrong with that approach but it has always seemed a bit excessive if you are only running a small home network which isn't exposed to the Internet, you don't want to incur the expense of purchasing a domain, and you simply want that irritating message to go away.\n\nThat's what this repository is for:\n\n* You can create a private [domain certificate](#domainCertificate) which you can install on each of your client systems (browsers). A single domain certificate lasts for as long as you like (the default is 10 years) and covers all of your [server](#serverCertificate) and [wildcard](#wildcardCertificate) certificates.\n* You can create server certificates for your servers and wildcard certificates for your reverse proxies which you will only need to refresh every year or so.\n* You won't need to register, pay for and manage an official domain. You won't need to sign-up with Let's Encrypt. And you won't need to set up an ACME client to auto-renew your certificates, nor trouble-shoot when things foul up.\n\nAs you eyeball the table of contents, you might think \"too hard\" but it really isn't. To the extent that there's any complexity, it's in the mechanics of installing the components on the various hosts. And that's mostly because, in the absence of any clear-cut standards, every designer of every system has invented their own way of supporting OpenSSL. Plus, even with [Let's Encrypt](https://letsencrypt.org) and [ACME](https://datatracker.ietf.org/doc/html/rfc8555), you often have to figure this bit out for yourself anyway. \n\n## contents\n\n- [on the subject of …](#onTheSubject)\n\n\t- [… a Domain](#onDomain)\n\t- [… a DNS server](#onDNSserver)\n\t- [… certificates](#onCerts)\n\n\t\t- [certificate authority](#certificateAuthority)\n\t\t- [domain certificate](#domainCertificate)\n\t\t- [server certificate](#serverCertificate)\n\t\t- [wildcard certificate](#wildcardCertificate)\n\n\t- [… client/server roles](#onClientServer)\n\n- [creating certificates - tutorial](#workedExample)\n\n\t- [create Certificate Authority](#makeCA)\n\t- [create server certificate](#makeServer)\n\t- [create wildcard certificate](#makeWildcard)\n\t- [extending the example](#extendedExample)\n\n- [scripts reference](#scriptsReference)\n\n\t- [`make_domain_certificate.sh`](#makeCAScript)\n\t- [`make_server_certificate.sh`](#makeServerScript)\n\t- [`make_wildcard_certificate.sh`](#makeWildcardScript)\n\t- [Unix installation scripts](#installUnixScripts)\n\n\t\t- [`install_domain_certificate.sh`](#installDomainScript)\n\t\t- [`install_server_package.sh`](#installServerScript)\n\n- [copying files to other hosts](#copyMethods)\n\n\t- [secure-shell methods](#copyViaSSH)\n\n\t\t- [secure copy (`scp`)](#copyViaSCP)\n\t\t- [SSH file-transfer protocol (`sftp`)](#copyViaSFTP)\n\t\t- [SSH File System (`sshfs`)](#copyViaSSHFS)\n\n\t- [network shares](#copyViaShare)\n\t- [email](#copyViaEMail)\n\t- [sneaker-net](#copyViaShoe)\n\n- [installation procedures](#installGrabBag)\n\n\t- [Unix (Linux and macOS)](#installUnix)\n\n\t\t- [Client only](#installUnixClient) (just the domain certificate)\n\t\t- [Server and Client](#installUnixServer) (domain and server certificates)\n\n\t- [Linux browser examples](#linuxBrowsers)\n\n\t\t- [Chromium](#installChromium)\n\t\t- [Firefox](#installFirefox)\n\n\t- [Docker container examples](#dockerContainers)\n\n\t\t- [Node-RED](#dockerNodered)\n\t\t- [Gitea](#dockerGitea)\n\t\t- [Grafana](#dockerGrafana)\n\t\t- [Zigbee2MQTT](#dockerZigbee)\n\n\t- [iOS/iPadOS devices](#installIOSclients)\n\t- [Nginx (reverse proxy)](#installNginx)\n\t- [Proxmox-VE servers](#installProxmoxSystems)\n\t- [Android](#installAndroid)\n\t- [Windows](#installWindows)\n\n- [useful commands](#usefulCommands)\n- [see also](#seeAlso)\n- [acknowledgement](#acknowledgement)\n- [history](#history)\n\n\u003chr\u003e\n\n\u003ca name=\"onTheSubject\"\u003e\u003c/a\u003e\n## on the subject of …\n\nPlease take the time to read this section. It explains fundamental concepts that will help you understand what the scripts do, and the purpose of the keys and certificates generated by the scripts.\n\n\u003ca name=\"onDomain\"\u003e\u003c/a\u003e\n### … a Domain\n\nYou *will* need a domain but that doesn't mean you have to register and pay for an \"official\" domain if you don't need it for some other reason. If you already own a domain, use it. If you don't, you can either invent something unique or you can adopt the `home.arpa` domain which is reserved by [RFC8375](https://datatracker.ietf.org/doc/html/rfc8375) for exactly this kind of situation.\n\nIf you decide to use `home.arpa`, I'd recommend inventing your own sub-domain. For example, if your family name is \"Williams\" and you live in \"Burns\" street, you might choose a sub-domain like one of the following:\n\n* `williams.home.arpa`\n* `burns.home.arpa`\n\nThe examples in this document use:\n\n* `your.home.arpa`\n\nSubstitute your own domain wherever you see that string.\n\n\u003ca name=\"onDNSserver\"\u003e\u003c/a\u003e\n### … a DNS server\n\nThe need for a domain doesn't imply that you also need a Domain Name System server. SSL\u0026nbsp;certificates can be made to work with IP addresses and hostnames in `/etc/hosts` but your life will definitely be much easier with a DNS server. A DNS server becomes almost mandatory if you want to run a reverse proxy service like Nginx.\n\nTip:\n\n* If you don't have your own DNS server but it has been on your to-do list for a while, then a quick way to get started is to use [Pi-hole](https://pi-hole.net). It's not just an ad-blocker, it's also a perfectly usable DNS server.\n\n\u003ca name=\"onCerts\"\u003e\u003c/a\u003e\n### … certificates\n\nThe word \"certificate\" gets a lot of use. In a practical sense, a certificate is the result of binding a public key with a \"subject\" and signing the pairing with a private key. Although you won't find all of these terms in official OpenSSL documentation, this guide uses the following definitions to try to keep the concepts clear:\n\n* **certificate authority** \u003ca name=\"certificateAuthority\"\u003e\u003c/a\u003e\n\n\tA Certificate Authority or \"CA\", is a standard private key generated for the sole purpose of being a CA. The private key is used to sign everything else so it is intended to be kept reasonably secure.\n\n* **domain certificate** \u003ca name=\"domainCertificate\"\u003e\u003c/a\u003e\n\n\tA domain certificate is a by-product of creating a [Certificate Authority](#certificateAuthority). It is a **domain** certificate because the \"subject\" is your domain (eg `your.home.arpa`). The domain certificate is signed by your CA's private key (rather than that of another authority further up the chain of trust) so it is said to be both \"self-signed\" and a \"root certificate\".\n\n\tYour CA's private key is also used to sign your **server** and **wildcard** certificates (which are discussed next). When you make your **domain** certificate available to a browser and mark the certificate trusted, the browser will implicitly trust any **server** or **wildcard** certificates that were signed by your CA.\n\n* **server certificate** \u003ca name=\"serverCertificate\"\u003e\u003c/a\u003e\n\n\tA server certificate is a non-root certificate which is signed by your [Certificate Authority](#certificateAuthority). It is a **server** certificate because the \"subject\" includes the valid ways by which the server can be reached, such as the server's:\n\n\t- host name (eg `boson`)\n\t- fully-qualified domain name (eg `boson.your.home.arpa`)\n\t- multicast domain name (eg `boson.local`)\n\n\tThe server certificate attests that the server is what it claims to be. You install the server certificate and server private key on the server for which it was generated.\n\n* **wildcard certificate** \u003ca name=\"wildcardCertificate\"\u003e\u003c/a\u003e\n\n\tA wildcard certificate is a non-root certificate which is signed by your [Certificate Authority](#certificateAuthority). The \"subject\" takes the form `*.your.home.arpa` where the asterisk implies \"any\".\n\n\tYou typically install the wildcard certificate and wildcard private key on reverse-proxy managers such as Nginx.\n\n\u003ca name=\"onClientServer\"\u003e\u003c/a\u003e\n### … client/server roles\n\nTCP/IP is a client-server model. Clients initiate \"connections\" while servers respond to connection requests from clients. This is true, irrespective of whether the client and server are running on the same or different hosts.\n\nFor SSL/TLS, this means that when a host is running in a:\n\n* *client* role, it needs access to the [**domain**](#domainCertificate) certificate so it can confirm the authenticity of the *server* with which it is initiating a connection (even if that server is running on the same host);\n* *server* role, it needs access to the [**server**](#serverCertificate) certificate and server private key so it can prove who it is when challenged by a *client* (even if that client is running on the same host). If a server provides reverse proxy services, it needs access to the [**wildcard**](#wildcardCertificate) certificate and wildcard private key.\n\nIn terms of TCP/IP, all computers can operate in both *client* and *server* roles. However, in the SSL/TLS context, the most common pattern you are likely to face in the average small home network is:\n\n* your desktops, laptops and portable devices will mainly operate in *client* roles; while\n* your Linux systems will mainly operate in *server* roles, unless a system is also running a desktop environment, in which case it may operate in both roles.\n\n\u003e I can't speak for current Windows systems but back in Mac\u0026nbsp;OS\u0026nbsp;X days (2015 and earlier), Macs were pretty darn good at running any service you cared to spin-up. Since then, each new macOS release has increased the level of difficulty such that it's usually simpler to run services on Raspberry Pis or Proxmox-VE guests.\n\n\u003chr\u003e\n\n\u003ca name=\"workedExample\"\u003e\u003c/a\u003e\n## creating certificates - tutorial\n\n[Figure 1](#figure1) is an example of a small home network.\n\n| \u003ca name=\"figure1\"\u003e\u003c/a\u003eFigure 1: Network Model             |\n|:---------------------------------------------------------:|\n|![Basic directory structure](./images/network-model-1.png) |\n\nThere are some clients (desktops, laptops, tablets, phones) running browsers, plus the following servers:\n\n* `lepton` is a [Proxmox-VE](https://www.proxmox.com/en/products/proxmox-virtual-environment/overview) virtualization platform. Proxmox-VE includes its own web-based user interface for managing guest systems, which communicates via HTTPS on port 8006.\n\n\tThe Proxmox-VE hypervisor is supervising:\n\n\t* `boson` which is a Debian guest system running two Docker containers.\n\n* `quark` is a Raspberry Pi also running two Docker containers, one of which is the Nginx reverse-proxy manager.\n\nThe three \"http\" containers are intended to represent the class of containers which offer a *service* that includes some kind of web-based user interface. Such containers are usually configured to communicate via HTTP but can sometimes be reconfigured to communicate via HTTPS. Good examples are Node-RED, Grafana and Zigbee2MQTT. \n\n\u003e When it comes to SSL, there are no practical differences between services which have been installed *natively*, or are running in Docker containers, nor whether those containers are operating in *host\u0026nbsp;mode* or *non-host\u0026nbsp;mode.*\n\nThe `nginx` container listens on three ports:\n\n* 80 supports HTTP-based reverse-proxy services\n* 443 supports HTTPS-based reverse-proxy services\n* 81 is the Nginx management interface (also HTTP)\n\nIn the vast majority of home networking situations you will be able to get away with letting Nginx do almost all of the work. Nginx will present the HTTPS interface to your clients while, behind the scenes, communication will continue to occur via HTTP, just as it did before. To be able to support HTTPS, Nginx needs a wildcard certificate for your domain.\n\nOtherwise, you will only need to generate server certificates in the following situations:\n\n1. if a particular service **insists** on communicating **solely** via HTTPS (ie you don't have a choice in the matter); or\n2. if you decide that a service should only communicate via HTTPS.\n\nWith that knowledge, you should be able to understand the deployment of the various SSL certificates in [Figure 1](#figure1):\n\n* `lepton` needs a server certificate (\u003c!--spade--\u003e\u0026#x2660;). This is because Proxmox-VE insists on communicating via HTTPS.\n* the `nginx` container is a special case. It needs a wildcard certificate (\u003c!--club--\u003e\u0026#x2663;).\n* each client needs a domain certificate (\u003c!--diamond--\u003e\u0026#x2666;). A domain certificate allows a client to establish HTTPS communications with services provisioned with server or wildcard certificates.\n\nLet's create the certificates you need for [Figure 1](#figure1).\n\n\u003ca name=\"makeCA\"\u003e\u003c/a\u003e\n### create Certificate Authority\n\nA Certificate Authority (CA) is, essentially, a private key that can be used to sign your domain, server and wildcard certificates. To create your CA, run this command:\n\n``` console\n$ ./make_domain_certificate.sh your.home.arpa\n```\n\nThe expected response is:\n\n```\nGenerating a private key for your Certificate Authority\nGenerating self-signed root certificate for the domain: your.home.arpa\n```\n\nYour domain certificate is also created by this process. [Figure 2](#figure2) shows the directory structure.\n\n| \u003ca name=\"figure2\"\u003e\u003c/a\u003eFigure 2: File tree: Domain Certificate |\n|:-------------------------------------------------------------:|\n|![Domain Certificate tree](./images/CA-tree.png)               |\n\nSee [`make_domain_certificate.sh`](#makeCAScript) for a detailed explanation of the directory contents.\n\n\u003e The private key and domain certificate each contain their own copies of the public key so the absence of an explicit `.pub` should not concern you. You will see this pattern repeated for server and wildcard certificates.\n\nThe critical element is the domain certificate\u0026nbsp;\u003c!--A--\u003e\u0026#x1F150;. This single certificate will cover **all** of your servers and reverse proxies, whether they exist now or are not yet a gleam in your eye.\n\nThe domain certificate (but not the private key) must be [installed](#installGrabBag) on clients where you run a browser or other TLS-aware utilities like `curl` and `wget`. Here are some links to instructions for common examples:\n\n* [Android](#installAndroid)\n* Debian desktop systems:\n\n\t1. [Install the domain certificate](#installUnixClient); then\n\t2. Tell each installed browser how to access the domain certificate:\n\n\t\t- [Chromium browser](#installChromium) and/or\n\t\t- [Firefox browser](#installFirefox)\n\n* [iOS devices](#installIOSclients)\n* [macOS systems](#installUnixClient)\n* [Windows](#installWindows)\n\nThe default lifetime of a domain certificate is 10 years so, in practice, it is likely to outlast any device on which it is deployed. \n\nIndeed, you can get into the habit of re-running the `make_domain_certificate.sh` script each time you get a new device. That will extend the certificate lifetime by another ten years. You only have to install the updated domain certificate on the newly-acquired device. You do **not** have to also propagate the updated domain certificate to all of your older devices because their existing domain certificates will continue to work.\n\nDomain certificates really are set-and-forget! \n\n\u003ca name=\"makeServer\"\u003e\u003c/a\u003e\n### create server certificate\n\nYou need to generate a private key and matching server certificate for each server where:\n\n1. You are running a web service; **and**\n2. You need to use HTTPS to reach that service.\n\nIn [Figure 1](#figure1), the only candidate meeting those criteria is the Proxmox-VE server running on `lepton`. Let's create it:\n\n``` console\n$ ./make_server_certificate.sh lepton your.home.arpa\n```\n\nThe expected response is:\n\n```\nGenerating private key for server: lepton\nGenerating Certificate Signing Request (CSR)\nGenerating server certificate for: lepton.your.home.arpa\nCertificate request self-signature ok\nsubject=CN=lepton.your.home.arpa\nX509v3 Subject Alternative Name: \n    DNS:lepton, DNS:lepton.your.home.arpa, DNS:lepton.local, DNS:localhost\n```\n\nThe last line contains important information that you would be wise to verify. When rewritten from its comma-separated form, the result is:\n\n```\nDNS:lepton\nDNS:lepton.your.home.arpa\nDNS:lepton.local\nDNS:localhost\n```\n\nWhat this indicates is that the server certificate is valid for all of these URLs:\n\n* `https://lepton` - the host's hostname\u0026nbsp;\u003csup\u003e†\u003c/sup\u003e\n\n\t\u003e \u003csup\u003e†\u003c/sup\u003e this assumes an entry in the client's `/etc/hosts`. It is a separate concept from the auto-appending of a search domain, which effectively converts the *hostname* form into the *fully-qualified domain name* form.\n\n* `https://lepton.your.home.arpa` - the host's fully-qualified domain name\n\n* `https://lepton.local` - the host's multicast domain name\n\n* `https://localhost` - the host's loopback address\n\n\nThe result of creating a server certificate is shown in [Figure 3](#figure3).\n\n| \u003ca name=\"figure3\"\u003e\u003c/a\u003eFigure 3: File tree: Server Certificate |\n|:-------------------------------------------------------------:|\n|![Server Certificate tree](./images/server-tree.png)           |\n\nSee [`make_server_certificate.sh`](#makeServerScript) for a detailed explanation of the directory contents. The critical elements are the server certificate\u0026nbsp;\u003c!--B--\u003e\u0026#x1F151; and private key\u0026nbsp;\u003c!--C--\u003e\u0026#x1F152; which, together with the domain certificate\u0026nbsp;\u003c!--A--\u003e\u0026#x1F150;, constitute the server's installation package\u0026nbsp;\u003c!--D--\u003e\u0026#x1F153;.\n\nBecause `boson` is a Proxmox-VE server, you install the server certificate\u0026nbsp;\u003c!--B--\u003e\u0026#x1F151; and key\u0026nbsp;\u003c!--C--\u003e\u0026#x1F152; using the [Proxmox-VE user interface](#installProxmoxSystems). Once those components are in place then any client which has been provisioned with your domain certificate will be able to reach Proxmox-VE without facing a \"connection is not private\" challenge.\n\n\u003e The server's installation package\u0026nbsp;\u003c!--D--\u003e\u0026#x1F153; isn't needed for this use-case.\n\nThe default lifetime of a server certificate is two years so you will need to regenerate the certificate from time to time. Technically, you *can* create the server certificate with a longer lifetime but your browsers may complain about it.\n\n\u003ca name=\"makeWildcard\"\u003e\u003c/a\u003e\n### create wildcard certificate\n\nThe `nginx` reverse proxy manager service is a special case. It needs to be provisioned with the wildcard certificate for your domain. Let's create it:\n\n``` console\n$ ./make_wildcard_certificate.sh your.home.arpa\n```\n\nThe expected response is:\n\n```\nGenerating private key for the wildcard domain: *.your.home.arpa\nGenerating Certificate Signing Request (CSR)\nGenerating certificate for the wildcard domain: *.your.home.arpa\nCertificate request self-signature ok\nsubject=CN=*.your.home.arpa\n```\n\nThe script creates the directory structure shown in [Figure 4](#figure4).\n\n| \u003ca name=\"figure4\"\u003e\u003c/a\u003eFigure 4: File tree: Wildcard Certificate |\n|:---------------------------------------------------------------:|\n|![Wildcard tree](./images/wildcard-tree.png)                     |\n\nSee [`make_wildcard_certificate.sh`](#makeWildcardScript) for a detailed explanation of the directory contents. The critical elements are the wildcard certificate\u0026nbsp;\u003c!--E--\u003e\u0026#x1F154; and private key\u0026nbsp;\u003c!--F--\u003e\u0026#x1F155;.\n\nFollow the instructions to install the wildcard certificate\u0026nbsp;\u003c!--E--\u003e\u0026#x1F154; and key\u0026nbsp;\u003c!--F--\u003e\u0026#x1F155; using the [Nginx user interface](#installNginx). After that, you can start adding proxy-host definitions. Then, any client which has been provisioned with your domain certificate will be able to use HTTPS-based URLs to reach servers defined by your reverse-proxy service.\n\nThe default lifetime of a wildcard certificate is two years so you will need to regenerate the certificate from time to time. As with server certificates, you *can* create wildcard certificates with longer lifetimes but your browsers may complain if you do.\n\n\u003ca name=\"extendedExample\"\u003e\u003c/a\u003e\n### extending the example\n\nLet's move the goal-posts slightly. [Figure 5](#figure5) changes one of the HTTP containers on each server to use HTTPS.\n\n| \u003ca name=\"figure5\"\u003e\u003c/a\u003eFigure 5: Additional Certificates  |\n|:--------------------------------------------------------:|\n|![Additional Certificates](./images/network-model-2.png)  |\n| \u003csup\u003e†\u003c/sup\u003e same certificate as already used on `quark` |\n\nGrafana is an example of a container which can be adapted to use HTTPS via a server certificate which was constructed for the host on which the container is running. To put this another way, if you have two or more such containers running on the same host, they can all use the same server certificate.\n\nGitea, on the other hand, is an example of a container that makes it hard (but not impossible) to use a server certificate which was constructed for the host on which it is running. Fortunately, Gitea comes with built-in facilities for generating and maintaining its own container-specific certificates. The [IOTstack wiki for Gitea](https://sensorsiot.github.io/IOTstack/Containers/Gitea/) contains the necessary instructions.\n\nSo that leaves Grafana, which means we need a server certificate for `boson`:\n\n``` console\n$ ./make_server_certificate.sh boson your.home.arpa\n```\n\nRecall that the server certificate for `lepton` was installed using the Proxmox-VE GUI. In this use-case, we are going to use the command line. The [installation process](#installUnixServer) begins with copying two files to `boson`:\n\n* an installer script named [`install_server_package.sh`](#installServerScript); plus\n* the server's installation package (refer \u0026nbsp;\u003c!--D--\u003e\u0026#x1F153; in [Figure\u0026nbsp;3](#figure3)).\n\nYou then [run the installer script](#installUnixServer) on `lepton`.\n\nOnce the server certificate is in place, you need to make adjustments to Grafana's service definition. The instructions are [here](#dockerGrafana).\n\nPlease keep in mind that implementing HTTPS on both Grafana and Gitea was a **choice**. Both services would have continued to work quite happily using HTTP, with Nginx providing an HTTPS front end to each. In reality, the only time you will need more than domain and wildcard certificates is if HTTPS is forced on you (like Proxmox-VE) or **you** decide to do it.\n\n\u003ca name=\"scriptsReference\"\u003e\u003c/a\u003e\n## scripts reference\n\n\u003ca name=\"makeCAScript\"\u003e\u003c/a\u003e\n### make\u0026#x005F;domain\u0026#x005F;certificate.sh\n\nThis command creates a Certificate Authority for your domain. The usage is:\n\n``` console\n$ {DAYS=«days»} ./make_domain_certificate.sh «domain»\n```\n\nYou should run this script before you run any of the other scripts.\n\nThe first time you run this script, it creates a \"domain directory\" which is named for your `«domain»`, plus a sub-directory named \"CA\". The script then generates a private key for your Certificate Authority (CA) plus a self-signed domain certificate.\n\nSee [Figure 2](#figure2) for an example of the contents of a CA directory.\n\nThe `CA` directory contains files with the following extensions:\n\n* `.crt` is the self-signed domain certificate. It is in Privacy Enhanced Mail (PEM) format.\n* `.der` is a copy of the `.crt` converted to Distinguished Encoding Rules (DER) format.\n\n\t\u003e The `.der` is a convenience and should only be used if your client system won't accept the `.crt`. Otherwise, it should be ignored.\n\n* `.key` is the PEM-format private key associated with your Certificate Authority. This file should be kept reasonably secure.\n* `.srl` is used by OpenSSL for managing serial numbers. You should leave this file alone.\n\nThe `.crt` file should be installed on any client machine with a browser which will connect to any of your servers. How you install this certificate depends on the operating system. In reality, the situation is a bit of a mess but the section on [installation procedures](#installGrabBag) should get you started.\n\nProviding you don't delete the private key, you can re-run this script to extend the lifetime of your domain certificate.\n\nThe default lifetime of the domain certificate created by this script is 3650 days (ten years). You can, if you wish, set a different lifetime via the `DAYS` environment variable. For example, to set a lifetime of one year:\n\n``` console\n$ DAYS=365 ./make_domain_certificate.sh your.home.arpa\n```\n\nAll that happens is the validity period of the domain certificate changes to start from \"now\" and expire after as many days as you specify. Any value you set for `DAYS` is cached and will be re-used until you change the value again.\n\n\u003ca name=\"makeServerScript\"\u003e\u003c/a\u003e\n### make\u0026#x005F;server\u0026#x005F;certificate.sh\n\nThis command creates the required files for installation on one of your servers. It must be run **after** `make_domain_certificate.sh`. The usage is:\n\n``` console\n$ {DAYS=n} make_server_certificate.sh hostname domain {name | IP ...}\n```\n\nThe `«hostname»` should match whatever the target server reports when you run:\n\n``` console\n$ hostname -s\n```\n\nThe first time you run this script, it creates a `servers` directory, plus a sub-directory which is named for `«hostname»`. The script then generates a private key for your server, plus a server certificate which is signed by your Certificate Authority's private key.\n\nSee [Figure 3](#figure3) for examples of the contents of server directories. Each sub-directory in the `servers` directory contains the following files:\n\n* `.days` is the cached server certificate lifetime (in days).\n* `.subject-alt-names` is a cached list of subject alternative names. It will only be present if you pass additional `«name»` or `«IP»` arguments on the command line.\n* `«hostname».key` is a PEM-format private key.\n* `«hostname».crt` is a PEM-format server certificate, signed by your Certificate Authority.\n* `«hostname».csr` is an intermediate file containing the certificate signing request. You should leave this file alone.\n* `«hostname»_etc-ssl.tar.gz` is a compressed tape archive containing:\n\n\t- `«hostname».crt` is the server's certificate\n\t- `«hostname».key` is the server's private key\n\t- `«domain».crt` is the domain certificate\n\nThe default lifetime of a certificate created by this script is 730 days. You can set a different certificate lifetime if you wish. For example, to set a lifetime of 90 days:\n\n``` console\n$ DAYS=90 ./make_server_certificate.sh «hostname» «domain»\n```\n\nYou should choose a lifetime within the range 1…825 days. Your certificate may not work if you set a value shorter than 1 day. If you set a lifetime longer than 825 days (2 years, 3 months) your browser may complain that your certificate is not standards compliant.\n\nThe certificate lifetime (whether the default value or a different value specified by you) is recorded in the `.days` cache. The cached value is then re-used until you specify a different value.\n\nThe \"subject alternative names\" is a list of IP addresses and/or domain names for which the certificate is authoritative. The default set is the network names via which the server is reachable:\n\n* `«hostname»` the (short) host name\n* `«hostname».«domain»` the fully-qualified domain name\n* `«hostname».local` the multicast DNS name\n\nYou can augment this list if necessary to include the host's IP address(es) and other domain names via which the server is reachable.\n\nAny time you augment the list, the result is recorded in the `.subject-alt-names` cache. The cached values are then re-used until you specify different values. In advanced situations you can hand-edit the cache and then run the script to generate a certificate according to your needs (eg omitting some of the standard defaults).\n\nProviding you don't delete anything in a server's sub-directory, you can re-run the same command to alter certificate parameters or extend the certificate's lifetime. For example, the following command simply extends the certificate's lifetime:\n\n``` console\n$ ./make_server_certificate.sh lepton your.home.arpa\n```\n\nAssuming the cached certificate lifetime was 730 days, the new certificate will have the same subject alternative names as its predecessor but its lifetime will be from \"now\" to \"730 days hence\".\n\nThe [`install_server_package.sh`](#installServerScript) script installs the contents of the `.tar.gz` on the server for which the server certificate was generated. \n\n\u003ca name=\"makeWildcardScript\"\u003e\u003c/a\u003e\n### make\u0026#x005F;wildcard\u0026#x005F;certificate.sh\n\nThis command creates the required files for a wildcard certificate. It must be run **after** `make_domain_certificate.sh`. The usage is:\n\n``` console\n$ {DAYS=n} ./make_wildcard_certificate.sh «domain»\n```\n\nThe first time you run this script, it generates a private key for your wildcard, plus a wildcard certificate for your domain which is signed by your Certificate Authority. The wildcard takes the form:\n\n```\n*.your.home.arpa\n```\n\nYou can only have a single level of wildcard so it follows that you can have exactly one wildcard certificate per domain. This is reflected in the fact that the generated files are stored in a dedicated `wildcard` sub-directory of your `«domain»` directory. Otherwise, with the exception of the `.tar.gz` which is not generated for wildcards, the files have the same extensions and meanings as [server certificates](#makeServerScript).\n\nSee [Figure 4](#figure4) for an example of the contents of a wildcard directory.\n\nA typical application for a wildcard certificate is a reverse proxy service such as Nginx.\n\nThe wildcard certificate lifetime is handled in the same way as was explained for [`make_server_certificate.sh`](#makeServerScript).\n\nThe subject alternative names for a wildcard certificate are fixed at `*.«domain»` and `*.local`.\n\nProviding you don't delete anything in the wildcard sub-directory, you can re-run the same command to extend the wildcard certificate's lifetime. For example, the following command simply extends the certificate's lifetime:\n\n``` console\n$ ./make_wildcard_certificate.sh your.home.arpa\n```\n\nAssuming the cached certificate lifetime was 730 days, the new certificate's lifetime will be from \"now\" to \"730 days hence\".\n\n\u003ca name=\"installUnixScripts\"\u003e\u003c/a\u003e\n### Unix installation scripts\n\nThe scripts in this section have been tested on Debian, macOS and Ubuntu. They have had limited testing on Alpine but have not been tested on other Linux distributions. The scripts will not run on non-Unix systems.\n\n\u003e As a general statement, these scripts will probably run on CoreOS and SUSE but won't run on other Linux distros. The main issue is the `update-ca-certificates` command not being available on those other distros. See the very useful discussion at this [GitHub repo](https://github.com/millermatt/osca) if you need to tailor these scripts.\n\nThese scripts have dependencies which you should check before running them for the first time:\n\n* on the supported Linux distros:\n\n\t``` console\n\t$ sudo apt update\n\t$ sudo apt install -y gnutls-bin p11-kit p11-kit-modules libnss3-tools ca-certificates\n\t```\n\n* on MacOS (assuming [HomeBrew](https://brew.sh) is installed):\n\n\t``` console\n\t$ brew update\n\t$ brew install coreutils\n\t```\n\nYou must be an administrator with the ability to run `sudo`. If your system is configured to prompt for a password when the `sudo` command is used, you can expect that to happen.\n\nOn macOS, you can also expect to have to respond to a security dialog when the script tries to add the trusted domain certificate to your keychain.\n\n\u003ca name=\"installDomainScript\"\u003e\u003c/a\u003e\n#### install\u0026#x005F;domain\u0026#x005F;certificate.sh\n\nThis script assumes that you want to run your Unix system in a client role only, which means only the domain certificate is needed.\n\n\u003e The [`install_server_package.sh`](#installServerScript) discussed below installs both your domain certificate **and** your server certificate and key so you do **not** need to run **both** of these scripts. The main reason this script exists is to avoid you having generate a server certificate for every client, just to get the domain certificate installed.\n\nPreconditions:\n\n1. This script, plus the *domain certificate* for the target host must be present in the working directory. The expected filename pattern for the certificate is:\n\n\t```\n\t«domain».crt\n\t```\n\n\twhere `«domain»` is the value you get from running the following command on the target client system:\n\n\t``` console\n\t$ hostname -d\n\t```\n\n\tSee [methods for copying files to other hosts](#copyMethods). There is a practical example [here](#installUnixClient).\n\n2. The subject of the domain certificate contained within the *installation package* must also match your `«domain»`.\n\nUsage:\n\n``` console\n$ {DOMAIN=«domain»} ./install_domain_certificate.sh\n```\n\nIf you encounter a situation where `hostname -s` does not match the name you used when generating the server certificate, you can force the installation like this:\n\n``` console\n$ DOMAIN=«domain» ./install_domain_certificate.sh\n```\n\nScript behaviour depends on your operating system:\n\n* on supported Linux systems, the domain certificate is copied to the path:  \n\n\t```\n\t/usr/local/share/ca-certificates/«domain»/«domain».crt\n\t```\n\n\tAfter that, the command `update-ca-certificates` is run, which propagates your domain certificate into `/etc/ssl/certs`. This allows the host to act in a *client* role when using commands like `curl` and `wget` in conjunction with TLS-based protocols, which all seem to work out of the box.\n\n\tLinux browsers generally need additional configuration before they will adopt a custom domain certificate. For examples, see:\n\n\t* [Firefox](#installFirefox); and\n\t* [Chromium](#installChromium)\n\n* on macOS hosts, providing the incoming domain certificate is not already resident in your user keychain, it will be added and marked trusted. Unfortunately, the macOS keychain will happily accept duplicate domain certificates, providing they have different expiration dates. There seems to be no way of automating the removal of superseded or expired domain certificates, which means it is left up to you to do using the macOS \"Keychain Access\" application. The script does, however, let you know how many such duplicates exist.\n\n\tAll macOS browsers seem to work out of the box. I have tested Safari, Chrome, Chromium-Gost, and Firefox.\n\n\tSome TLS-aware command-line utilities can also use a domain certificate resident in your keychain while other tools can be a bit hit and miss. Examples include the HomeBrew versions of `curl` and `wget`. This script solves that problem by adding the necessary directives to `~/.curlrc` and `~/.wgetrc`.\n\n\tIf you come across another TLS-aware tool that doesn't respect the domain certificate in your keychain, you will have to figure out a similar solution.\n\n\u003ca name=\"installServerScript\"\u003e\u003c/a\u003e\n#### install\u0026#x005F;server\u0026#x005F;package.sh\n\nThe script assumes that you may wish to run your Unix system in **both** client and server roles. The former role needs the domain certificate while the latter needs the server certificate and key. The script installs all three. If you **only** need your Unix system to run in a client role, you should use the [`install_domain_certificate.sh`](#installDomainScript) script discussed above.\n\nPreconditions:\n\n1. This script, plus the *installation package* for the target host must be present in the working directory. The expected filename pattern for the package is:\n\n\t```\n\t«hostname»_etc-ssl.tar.gz\n\t```\n\n\twhere `«hostname»` is the value you get from running the following command on your server:\n\n\t``` console\n\t$ hostname -s\n\t```\n\n\tSee [methods for copying files to other hosts](#copyMethods). There is a practical example [here](#installUnixServer).\n\n2. The subject of the server certificate contained within the *installation package* must also match `«hostname»`. In other words, if you rename a host you **must** generate a new server certificate using the new name. You can't just rename the files.\n\nUsage:\n\n``` console\n$ {SERVER_HOSTNAME=«hostname»} ./install_server_package.sh\n```\n\nIf you encounter a situation where `hostname -s` does not match the name you used when generating the server certificate, you can force the installation like this:\n\n``` console\n$ SERVER_HOSTNAME=«hostname» ./install_server_package.sh\n```\n\n[Figure 10](#figure10) summarises what is installed.\n\n| \u003ca name=\"figure10\"\u003e\u003c/a\u003eFigure 10: Unix Certificates Installation |\n|:----------------------------------------------------------------:|\n|![Unix Certificates Installation](./images/install-unix.png)      |\n\nThis structure provides the necessary framework for most situations:\n\n* server roles need access to the server certificate\u0026nbsp;\u003c!--B--\u003e\u0026#x1F151; and server private key\u0026nbsp;\u003c!--C--\u003e\u0026#x1F152; This is the case irrespective of whether a service is running in a container or has been installed natively.\n* client roles need access to the domain certificate\u0026nbsp;\u003c!--A--\u003e\u0026#x1F150; but the certificate *in this location* is primarily intended for a container running its own client, such as a health-check service that relies on `curl` or `wget`.\n\nThe domain certificate ***in this location*** is (mostly) not useful for other client usages, such as browsers and command-line utilities like `curl` or `wget`. For those *other* client usages, the script also installs your domain certificate in the operating system's standard location. In this respect, the script's behaviour is identical to [`install_domain_certificate.sh`](#installDomainScript).\n\nPlease note the presence of symbolic links in [Figure 10](#figure10). Your domain certificate will have a name like `your.home.arpa.crt` while your server certificate and key will be named for your host (eg `lepton.crt` and `lepton.key`). I recommend using the machine-independent symbolic links (`domain.crt`, `localhost.crt` and `localhost.key`). That way, any absolute paths embedded in Docker service definitions or configuration files will be portable.\n\nRe-running this script always replaces any prior versions of the files in [Figure 10](#figure10), irrespective of whether the *installation package* actually contains newer files.\n\n\u003ca name=\"copyMethods\"\u003e\u003c/a\u003e\n## copying files to other hosts\n\nOne of the things you need to get comfortable with is moving files from your *support host* (the computer where you generate your certificates) onto the *target host* (the computer where the certificates need to be installed). The choice of method is up to you.\n\n\u003ca name=\"copyViaSSH\"\u003e\u003c/a\u003e\n### secure-shell (SSH) methods\n\nThe options here include:\n\n\n* the `scp` (\"secure copy\") command where the basic syntax is \u003ca name=\"copyViaSCP\"\u003e\u003c/a\u003e:\n\n\t``` console\n\t$ scp «file» {«file» ...} «user»@«target».«domain»:.\n\t```\n\n\tThe `«file»` argument can be repeated to transfer multiple files to the destination host.\n\n\n* the `sftp` (\"SSH file-transfer protocol\") command where the basic syntax is \u003ca name=\"copyViaSFTP\"\u003e\u003c/a\u003e:\n\n\t``` console\n\t$ sftp «user»@«target».«domain»:.\n\tsftp\u003e put «file1»\n\tsftp\u003e put «file2»\n\tsftp\u003e bye\n\t$\n\t```\n\n\tNote the multiple `put` commands. Unlike with `scp`, you can't use `sftp` to transfer multiple files using a single `put` command. For example:\n\n\t``` console\n\tsftp\u003e put «file1» «file2»\n\t```\n\n\tcopies `«file1»` from the local host, giving it the name `«file2»` on the remote host.\n\n\n* network shares using `sshfs` (the \"SSH File System\") \u003ca name=\"copyViaSSHFS\"\u003e\u003c/a\u003e:\n\n\t``` console\n\t$ MOUNTPOINT=\"$HOME/remote\"\n\t$ mkdir -p \"$MOUNTPOINT\"\n\t$ sshfs «user»@«target».«domain»:. \"$MOUNTPOINT\" -ovolname=\"«target»\"\n\t```\n\n\tOnce the volume is mounted, you can drag-and-drop like any other network share.\n\nNotes:\n\n1. If you have already set up password-less SSH then the instances of:\n\n\t```\n\t«user»@«target».«domain»\n\t```\n\n\twill reduce to just:\n\n\t```\n\t«target»\n\t```\n\n\tFor more information, see:\n\n\t* [`ssh-keygen` plus `ssh-copy-id` example](https://github.com/Paraphraser/IOTstackBackup/blob/master/ssh-tutorial.md)\n\t* [SSH certificates](https://github.com/Paraphraser/ssh-certificates)\n\n2. In each case, the `:` (colon) separates the host specification from the remote path specification. The colon separator is required for `scp` and `sshfs` but can be omitted for `sftp`.\n3. The trailing `.` implies the home directory of `«user»` on the `«target»` host. You can replace `.` with a path to another directory on the remote host. If a path does not start with `/` it is assumed to be relative to the user's home directory.\n\n\u003ca name=\"copyViaShare\"\u003e\u003c/a\u003e\n### network shares\n\nOther network share services such as Samba, the setup and usage of which is beyond the scope of this document.\n\n\u003ca name=\"copyViaEMail\"\u003e\u003c/a\u003e\n### email\n\nEmail can be used, providing both the sending and receiving device are configured appropriately. This is the recommended method for deploying domain certificates on iOS devices.\n\n\u003ca name=\"copyViaShoe\"\u003e\u003c/a\u003e\n### sneaker-net\n\n*Sneaker-net* is the option of last resort, where you use removable media like a \"thumb\" drive.\n\n\u003ca name=\"installGrabBag\"\u003e\u003c/a\u003e\n## installation procedures\n\nThis section is a grab-bag of installation methods in no particular order. As noted in the introduction, the variety of approaches and lack of consistency is largely the result of the absence of clear standards underpinning OpenSSL.\n\n\u003ca name=\"installUnix\"\u003e\u003c/a\u003e\n### Unix (Linux and macOS)\n\nThe procedure you need to follow depends on whether your target host is:\n\n* **only** a client running browsers and TLS-aware command-line tools; or\n* runs one or more HTTPS services.\n\nAlthough it will do no harm if you follow both procedures on any given target host, it is not necessary. The [SSL client only](#installUnixClient) procedure exists to save you from needing to generate server certificates for hosts that don't actually offer any HTTPS services.\n\n\u003ca name=\"installUnixClient\"\u003e\u003c/a\u003e\n#### Client only\n\nIf the target host only needs to behave as a **client** (ie run a browser or use TLS-aware command line utilities like `curl` and `wget`) then you should:\n\n1. Use one of the [copy methods](#copyMethods) to copy these files to the target host:\n\n\t* `install_domain_certificate.sh` (installation script)\n\t* `«domain».crt` (domain certificate\u0026nbsp;–\u0026nbsp;refer \u0026nbsp;\u003c!--A--\u003e\u0026#x1F150; in [Figure\u0026nbsp;2](#figure2))\n\n2. Connect to the host (eg open a terminal window or use SSH).\n\n3. Run the installation script:\n\n\t``` console\n\t$ ./install_domain_certificate.sh\n\t```\n\n\tSee [`install_domain_certificate.sh`](#installDomainScript) for more information on this script.\n\n\u003ca name=\"installUnixServer\"\u003e\u003c/a\u003e\n#### Server and Client\n\nIf the target host is running HTTPS-based **services** which need access to a custom server certificate then you should: \n\n1. Use one of the [copy methods](#copyMethods) to copy these files to the target host:\n\n\t* `install_server_package.sh` (installation script)\n\t* `«server»_etc-ssl.tar.gz` (installation package\u0026nbsp;–\u0026nbsp;refer \u0026nbsp;\u0026nbsp;\u003c!--D--\u003e\u0026#x1F153; in [Figure\u0026nbsp;3](#figure3))\n\n2. Connect to the host (eg open a terminal window or use SSH).\n\n3. Run the installation script:\n\n\t``` console\n\t$ ./install_server_package.sh\n\t```\n\n\tSee [`install_server_package.sh`](#installServerScript) for more information on this script. This script also installs your domain certificate so you do not need the [SSL Client only](#installUnixClient) procedure as well.\n\n\u003ca name=\"linuxBrowsers\"\u003e\u003c/a\u003e\n### Linux browser examples\n\n\u003ca name=\"installChromium\"\u003e\u003c/a\u003e\n#### Chromium\n\nChromium is not always pre-installed on Linux:\n\n``` console\n$ sudo apt update\n$ sudo apt install -y chromium\n```\n\nChromium (on Linux) maintains its own trust store so you need to tell it to import its own copy of your domain certificate. The instructions here assume you have already run one of the installation scripts. On Linux that places your domain certificate in the following directory:\n\n```\n/usr/local/share/ca-certificates\n```\n\n[Figure 6](#figure6) summarises the configuration process for the Chromium browser.\n\n| \u003ca name=\"figure6\"\u003e\u003c/a\u003eFigure 6: Debian+Chromium browser installation    |\n|:-----------------------------------------------------------------------:|\n|![Debian+Chromium browser installation](./images/add-linux-chromium.png) |\n\n1. Launch Chromium.\n2. Click \u003ckbd\u003e\u003c!--v.ellipsis--\u003e\u0026#x22EE;\u003c/kbd\u003e\u0026nbsp;\u003c!--A--\u003e\u0026#x1F130; at the end of the URL bar and choose \"Settings\"\u0026nbsp;\u003c!--B--\u003e\u0026#x1F131;.\n3. From the side-bar, choose \"Privacy and security\"\u0026nbsp;\u003c!--C--\u003e\u0026#x1F132;.\n4. Click the \"Security\" grouping\u0026nbsp;\u003c!--D--\u003e\u0026#x1F133;.\n5. Scroll down until you see \"Manage certificates\"\u0026nbsp;\u003c!--E--\u003e\u0026#x1F134; then click on that grouping.\n6. Click on the \"Authorities\" tab\u0026nbsp;\u003c!--F--\u003e\u0026#x1F135;.\n7. Click \u003ckbd\u003eImport\u003c/kbd\u003e\u0026nbsp;\u003c!--G--\u003e\u0026#x1F136; and navigate to:\n\n\t* `/usr/local/share/ca-certificates/`\u0026nbsp;\u003c!--H--\u003e\u0026#x1F137;\n\n8. Select your domain certificate (eg `your.home.arpa.crt`)\u0026nbsp;\u003c!--I--\u003e\u0026#x1F138; and click \u003ckbd\u003eSelect\u003c/kbd\u003e\u0026nbsp;\u003c!--J--\u003e\u0026#x1F139;.\n9. Turn on the \"Trust this certificate for identifying websites\" option\u0026nbsp;\u003c!--K--\u003e\u0026#x1F13A;, then click \u003ckbd\u003eOK\u003c/kbd\u003e\u0026nbsp;\u003c!--L--\u003e\u0026#x1F13B;.\n\nThe domain certificate is now installed and trusted\u0026nbsp;\u003c!--M--\u003e\u0026#x1F13C;.\n\nSee also:\n\n* [Chromium Certificate Management](https://chromium.googlesource.com/chromium/src.git/+/refs/heads/main/docs/linux/cert_management.md)\n\n\u003ca name=\"installFirefox\"\u003e\u003c/a\u003e\n#### Firefox\n\nFirefox is usually pre-installed on desktop systems. The instructions here assume you have already run one of the installation scripts and explain how to configure Firefox to use the standard certificate store.\n\nBegin by running the following command in a Terminal window:\n\n``` console\n$ dpkg --listfiles p11-kit-modules | grep trust\n``` \n\nThe expected response is:\n\n```\n/usr/lib/x86_64-linux-gnu/pkcs11/p11-kit-trust.so\n```\n\nIf you get a different answer, make a note of the path because you will need it later. The steps are:\n\n1. Click the\u0026nbsp;\u003c!--hamburger--\u003e\u0026#x2630; icon (\"Open Application Menu\") and choose \"Settings\".\n2. Choose \"Privacy \u0026 Security\".\n3. Scroll down to \"Security\". Find and click \u003ckbd\u003eSecurity Devices...\u003c/kbd\u003e.\n4. Click \u003ckbd\u003eLoad\u003c/kbd\u003e.\n5. Enter a name for the module like \"local trust\".\n6. Click \u003ckbd\u003eBrowse...\u003c/kbd\u003e.\n7. Choose \"Other Locations\", then choose \"This Computer\".\n8. Navigate to the `/usr/lib/x86_64-linux-gnu/pkcs11` directory, select `p11-kit-trust.so` and click \u003ckbd\u003eOpen\u003c/kbd\u003e.\n\n\t\u003e On smaller screens you may need to click \"Activities\" on the menu bar to cause the dialog from the prior step to become available as a thumbnail, after which you can select it so it is brought to the front.\n\n9. Click \u003ckbd\u003eOK\u003c/kbd\u003e to load the driver.\n10. Click \u003ckbd\u003eOK\u003c/kbd\u003e to close the Device Manager.\n11. Close all open windows or tabs (which causes Firefox to quit).\n\nWhen you re-launch Firefox it will have access to your domain certificate.\n\nSee also:\n\n* [Firefox Private Certificate Authority](https://wiki.debian.org/Firefox/PrivateCertificateAuthority)\n\n\u003ca name=\"dockerContainers\"\u003e\u003c/a\u003e\n### Docker container examples\n\nThis discussion is confined to Docker containers running on Linux hosts. *Some* of it may be applicable to Docker Desktop environments on macOS or Windows but I have not tested that.\n\nAs a rule of thumb, it is usually better to allow containers to work \"as supplied\" (which normally means HTTP) and use a reverse proxy such as Nginx to present the HTTPS interface to clients. If you accept this advice you will minimise the extent to which you need to deploy server certificates, and avoid the need to tailor container service definitions and configuration files.\n\nHowever, assuming you intend to *ignore* that advice\u0026nbsp;…\n\nAlthough Docker containers behave like a small independent computers with their own operating systems, hostnames and (if running in non-host mode) IP addresses, it's the perspective of the **client** that actually matters for SSL. The **client** needs to be assured that it's communicating with the **host**, not something running on the host. That's why, in general, it's the **host** that needs a server certificate, rather than each HTTPS container. \n\nThat said, every container is unique. Some support HTTPS, some don't. For those that do, the implementation mechanisms differ. Some expect you to provide the certificate; others include their own certificate-generation capabilities. Activating HTTPS on a container typically involves some combination of:\n\n1. An additional bind-mount, to give the container access to the server certificate and key of the host on which the container is running.\n\n2. Environment variables and/or edits to configuration files, to tell processes running inside the container where to find the server certificate and key.\n\nNginx is a special case which needs to be [provisioned](#installNginx) with your wildcard certificate and key. We'll ignore it for now.\n\nAssuming you have performed the [SSL Server and Client](#installUnixServer) procedure **and** that you accept the recommendation to use the machine-independent symbolic links, your starting point is:\n\n* `/opt/local/etc/openssl/certs/domain.crt` (domain certificate)\n* `/opt/local/etc/openssl/certs/localhost.crt` (server certificate)\n* `/opt/local/etc/openssl/private/localhost.key` (server key)\n\nThe container will need access to those files and the simplest way to accomplish that is with a bind-mount added to the container's service definition:\n\n``` yaml\nvolumes:\n  - /opt/local/etc/openssl:/opt/local/etc/openssl:ro\n```\n\n\u003e Before adding a new bind-mount specification to a container, it is a good idea to go into the container to see whether it already defines the internal (right-hand-side) path. If it does, replace the right-hand-side path with something unique and adapt these instructions accordingly.\n\nThe container then needs to be told to use:\n\n* the server certificate at `/opt/local/etc/openssl/certs/localhost.crt`\n* the server private key at `/opt/local/etc/openssl/private/localhost.key`\n\nAny internal clients (eg health-check services) have the domain certificate available at:\n\n* `/opt/local/etc/openssl/certs/domain.crt`\n\nOne possible fly in the ointment is if you have a container which either launches non-root (ie a `user:` clause) or launches as root (the default) but downgrades its privileges at runtime. Please study [Figure 10](#figure10). Although the domain and server certificates are world-readable, the server private key is not. This implements best practice for the protection of private keys.\n\nIf this causes a problem you can either relax the permissions on your system, or copy the private key into the scope of the problematic container and set appropriate permissions there.\n\nNote:\n\n* Once the structures in `/opt/local/etc/openssl/private` have been established, re-running the [installer script](#installServerScript) will not alter either ownership or permissions.\n\n\u003ca name=\"dockerNodered\"\u003e\u003c/a\u003e\n#### Node-RED\n\nNode-RED is an example of a container where HTTPS is activated by editing its configuration file. You need to:\n\n1. Add the bind-mount explained above to the service definition:\n\n\t``` yaml\n\tvolumes:\n\t  - /opt/local/etc/openssl:/opt/local/etc/openssl:ro\n\t```\n\n2. Edit `settings.js` to:\n\n\t* activate the `fs` module (near the top of the file); and\n\t* uncomment and edit this block of JSON:\n\n\t\t``` JSON\n\t\thttps: {\n\t\t    key: fs.readFileSync('/opt/local/etc/openssl/private/localhost.key'),\n\t\t    cert: fs.readFileSync('/opt/local/etc/openssl/certs/localhost.crt')\n\t\t}, \n\t\t```\n\n3. Recreate the container:\n\n\t``` console\n\t$ docker compose up -d nodered\n\t```\n\n4. If you need to re-edit `settings.js` for any reason (eg to fix a typo), an `up` command will not work. You need to restart the container:\n\n\t``` console\n\t$ docker compose restart nodered\n\t```\n\nNote:\n\n* The IOTstack service definition for Node-RED includes a [`user: \"0\"`](https://github.com/SensorsIot/IOTstack/blob/940f96b74689353047d75e42380f6688b0ce48c4/.templates/nodered/service.yml#L9) clause which forces the container to launch and run as root. This was done for other reasons but it has the beneficial side-effect of giving the container access to the server private key.\n\n\u003ca name=\"dockerGitea\"\u003e\u003c/a\u003e\n#### Gitea\n\nGitea is an example of a container that can generate its own certificates. This means:\n\n1. The service definition does not need access to `/opt/local/etc/openssl`;\n2. The self-signed certificate generated by the container pulls double duty as both the certificate authority certificate and server certificate; and\n3. The `healthcheck` clause needs to reference the container by its name (`gitea`) rather than relying on `localhost`.\n\nThe practical consequence of this arrangement is that it is only really useful (in the sense of SSL working properly) if the **service** domain name (eg `gitea.your.home.arpa`) is an alias (CNAME) pointing to either:\n\n* the host on which the Gitea service is running; or\n* the host on which a reverse proxy like Nginx is running.\n\nSee [IOTstack Gitea](https://sensorsiot.github.io/IOTstack/Containers/Gitea/) for detailed configuration instructions.\n\n\u003ca name=\"dockerGrafana\"\u003e\u003c/a\u003e\n#### Grafana\n\nThe [IOTstack service definition for Grafana](https://github.com/SensorsIot/IOTstack/blob/master/.templates/grafana/service.yml) is an example of a container with its own client (`healthcheck` clause), but which does not have its own certificate-generation capabilities, and where HTTPS activation is controlled by environment variables.\n\nRecall that the reason why a container uses the certificate and key of the host on which it is running is because it is the perspective of the client that matters. Most clients are external so their perspective is that they are communicating with the host, not the container.\n\nConversely, when a client is internal to the container, its perspective is that it is communicating with the container, not the host on which the container is running.\n\nAssumptions:\n\n1. Grafana is running on `boson` in [Figure 5](#figure5);\n2. A server certificate was generated for `boson`; and\n3. The server package was installed. \n\nProcedure:\n\n1. Give the container access to the certificates by adding the required bind-mount to the Grafana service definition:\n\n\t``` yaml\n\tvolumes:\n\t  - /opt/local/etc/openssl:/opt/local/etc/openssl:ro\n\t```\n\n4. Activate HTTPS on the Grafana container:\n\n\t``` yaml\n\tenvironment:\n\t  - GF_SERVER_CERT_KEY=/opt/local/etc/openssl/private/localhost.key\n\t  - GF_SERVER_CERT_FILE=/opt/local/etc/openssl/certs/localhost.crt\n\t  - GF_SERVER_PROTOCOL=https\n\t```\n\n\tGrafana (the process) now knows where to find the augmented server certificate and key.\n\n5. The baseline IOTstack health-check is defined like this:\n\n\t``` yaml\n\thealthcheck:\n\t  test: [\"CMD\", \"wget\", \"-O\", \"/dev/null\", \"http://localhost:3000\"]\n\t  interval: 30s\n\t  timeout: 10s\n\t  retries: 3\n\t  start_period: 30s\n\t```\n\n\tIssues with the specified `test:` include:\n\n\t* `http` is the wrong protocol. It needs to be `https`.\n\t*  Grafana is built on Alpine. The `wget` utility supplied with Alpine isn't fully TLS-aware  so `curl` is the better choice.\n\t*  Seeing as we're here, we may as well update the syntax too.\n\n\tReplacement test:\n\n\t``` yaml\n\t  test: [\"CMD-SHELL\", \"curl -sf4 --cacert /opt/local/etc/openssl/certs/domain.crt -o /dev/null https://localhost:3000\"]\n\t```\n\n\tIn this context, 3000 is the internal (container) port.\n\n6. Recreate the container:\n\n\t``` console\n\t$ docker compose up -d grafana\n\t```\n\nWith this structure in place, you can expect the following tests to succeed:\n\n1. Execution within the container:\n\n\t``` console\n\t$ docker exec grafana curl -f4 --cacert /opt/local/etc/openssl/certs/domain.crt -o /dev/null https://localhost:3000\n\t```\n\n\tHere, `localhost` means \"this container\" so `3000` is the internal port.\n\n2. Execution on the host on which the container is running:\n\n\t``` console\n\t$ curl -f4 -o /dev/null https://localhost:3000\n\t```\n\n\tIn this case, `localhost` means \"this host\" so `3000` is the external port. The `--cacert` option isn't needed because `curl` uses the host's certificate store in `/etc/ssl`.\n\n3. Using the fully-qualified domain name of the **host** where Grafana is running:\n\n\t``` console\n\t$ curl -f4 -o /dev/null https://boson.your.home.arpa:3000\n\t```\n\n\tThis works from any host where the domain certificate is available.\n\n4. Using the fully-qualified domain name of the Grafana **service**.\n\n\t``` console\n\t$ curl -f4 -o /dev/null https://grafana.your.home.arpa\n\t```\n\n\tIn this form, `https` defaults to port 443. `grafana.your.home.arpa` is assumed to point to your Nginx proxy, which sends the traffic back to `boson` on port 3000.\n\n\u003ca name=\"dockerZigbee\"\u003e\u003c/a\u003e\n#### Zigbee2MQTT\n\nZigbee2MQTT is similar to Grafana. All HTTPS-related configuration is done via environment variables.\n\nProcedure:\n\n1. Add the bind-mount explained above to the service definition:\n\n\t``` yaml\n\tvolumes:\n\t  - /opt/local/etc/openssl:/opt/local/etc/openssl:ro\n\t```\n\n2. By default, Zigbee2MQTT listens for HTTP communication on port 8080. However, the port switches to HTTPS if you define the following environment variables:\n\n\t``` yaml\n\tenvironment:\n\t- ZIGBEE2MQTT_CONFIG_FRONTEND_SSL_KEY=/opt/local/etc/openssl/private/localhost.key\n\t- ZIGBEE2MQTT_CONFIG_FRONTEND_SSL_CERT=/opt/local/etc/openssl/certs/localhost.crt\n\t```\n\n3. Recreate the container:\n\n\t``` console\n\t$ docker compose up -d zigbee2mqtt\n\t```\n\n\u003ca name=\"installIOSclients\"\u003e\u003c/a\u003e\n### iOS/iPadOS devices\n\nThe simplest way to get started is to email your domain certificate (eg `your.home.arpa.crt`) to a mailbox that you can access from your iOS device. This will often be your iCloud account.\n\n[Figure 7](#figure7) summarises the installation process.\n\n| \u003ca name=\"figure7\"\u003e\u003c/a\u003eFigure 7: iOS installation - starting from Mail |\n|:---------------------------------------------------------------------:|\n|![iOS installation starting from Mail](./images/add-ios.png)           |\n\nThe steps are:\n\n1. Launch Mail\u0026nbsp;\u003c!--A--\u003e\u0026#x1F130; and select the account to which you sent the domain certificate.\n2. Select the message\u0026nbsp;\u003c!--B--\u003e\u0026#x1F131; containing the attached domain certificate, then tap on the attachment's icon\u0026nbsp;\u003c!--C--\u003e\u0026#x1F132; (not the download symbol). iOS responds with a dialog telling you that a profile has been downloaded. Tap \u003ckbd\u003eClose\u003c/kbd\u003e\u0026nbsp;\u003c!--D--\u003e\u0026#x1F133;.\n3. Switch to the System Settings app\u0026nbsp;\u003c!--E--\u003e\u0026#x1F134;. Near the top of the left-hand panel is a temporary category named \"Profile Downloaded\"\u0026nbsp;\u003c!--F--\u003e\u0026#x1F135;. Tap that.\n4. A sheet slides in where you can see the certificate details. Tap the \u003ckbd\u003eInstall\u003c/kbd\u003e button\u0026nbsp;\u003c!--G--\u003e\u0026#x1F136; and follow the process to completion by tapping \u003ckbd\u003eInstall\u003c/kbd\u003e three more times (\u003c!--H--\u003e\u0026#x1F137;,\u0026nbsp;\u003c!--I--\u003e\u0026#x1F138; and\u0026nbsp;\u003c!--J--\u003e\u0026#x1F139;). Depending on your settings, iOS may prompt you to authenticate.\n5. At the top level of of \"System Settings\", tap \u003ckbd\u003eGeneral\u003c/kbd\u003e\u0026nbsp;\u003c!--K--\u003e\u0026#x1F13A;, then \u003ckbd\u003eAbout\u003c/kbd\u003e\u0026nbsp;\u003c!--L--\u003e\u0026#x1F13B;.\n6. Find and tap \u003ckbd\u003eCertificate\u0026nbsp;Trust\u0026nbsp;Settings\u003c/kbd\u003e\u0026nbsp;\u003c!--M--\u003e\u0026#x1F13C; (it's near the bottom of the list).\n7. A sheet opens where you can see the name of the domain certificate you have just installed. Turn on the switch\u0026nbsp;\u003c!--N--\u003e\u0026#x1F13D; and acknowledge the alert\u0026nbsp;\u003c!--O--\u003e\u0026#x1F13E; to enable full trust. Depending on your settings, iOS may also prompt you to authenticate.\n\n\u003ca name=\"installNginx\"\u003e\u003c/a\u003e\n### Nginx (reverse proxy)\n\nThe instructions in this section assume you have previously installed and initialised your [Nginx container](https://nginxproxymanager.com), and are able to login.\n\nAt the time of writing, [Pull Request 802](https://github.com/SensorsIot/IOTstack/pull/802) had been submitted to add Nginx to IOTstack. Once that is approved, the documentation should appear [here](https://sensorsiot.github.io/IOTstack/Containers/Nginx/). In the meantime:\n\n* [Documentation](https://github.com/SensorsIot/IOTstack/blob/6865efa9e7ec2170c7c0cec7ef241f5ec07f9782/docs/Containers/Nginx.md)\n* [Service definition](https://github.com/SensorsIot/IOTstack/blob/6865efa9e7ec2170c7c0cec7ef241f5ec07f9782/.templates/nginx/service.yml)\n\n[Figure 8](#figure8) summarises the wildcard certificate installation process using the web GUI.\n\n| \u003ca name=\"figure8\"\u003e\u003c/a\u003eFigure 8: Nginx Proxy Manager - add via GUI |\n|:-----------------------------------------------------------------:|\n|![Nginx Proxy Manager - add via GUI](./images/add-nginx-gui.png)   |\n\nThe steps are:\n\n1. Connect to the Nginx server GUI on port 81 (HTTP). For example:\n\n\t```\n\thttp://quark.your.home.arpa:81\n\t```\n\n2. Login using your credentials. \n3. Switch to the \"SSL Certificates\" tab\u0026nbsp;\u003c!--A--\u003e\u0026#x1F130;.\n4. Click \u003ckbd\u003eAdd SSL Certificate\u003c/kbd\u003e\u0026nbsp;\u003c!--B--\u003e\u0026#x1F131; and choose \"Custom\"\u0026nbsp;\u003c!--C--\u003e\u0026#x1F132; from the popup menu.  \n5. Type a name for your certificate at\u0026nbsp;\u003c!--D--\u003e\u0026#x1F133;. It might help you to remember that it is a wildcard certificate if you use a name like \"wildcard\" or \"*.your.home.arpa\".\n6. Click \u003ckbd\u003eBrowse\u003c/kbd\u003e\u0026nbsp;\u003c!--E--\u003e\u0026#x1F134;, then navigate to and select the `wildcard.key` file.\n7. Click \u003ckbd\u003eBrowse\u003c/kbd\u003e\u0026nbsp;\u003c!--F--\u003e\u0026#x1F135;, then navigate to and select the `wildcard.crt` file.\n8. Ignore the \"Intermediate Certificate\" field.\n9. Click \u003ckbd\u003eSave\u003c/kbd\u003e\u0026nbsp;\u003c!--G--\u003e\u0026#x1F136;.\n10. The newly-added certificate appears in the list at\u0026nbsp;\u003c!--H--\u003e\u0026#x1F137;.\n11. You can start using the certificate in the \"Hosts\" tab\u0026nbsp;\u003c!--J--\u003e\u0026#x1F139;.\n\n\u003ca name=\"installProxmoxSystems\"\u003e\u003c/a\u003e\n### Proxmox-VE servers\n\nAlthough Proxmox-VE is based on Debian, it is treated here as a special case.\n\n[Figure 9](#figure9) summarises the certificate installation process using the web GUI.\n\n| \u003ca name=\"figure9\"\u003e\u003c/a\u003eFigure 9: Proxmox-VE - add via GUI |\n|:--------------------------------------------------------:|\n|![Proxmox - add via GUI](./images/add-proxmox-gui.png)    |\n\nThe steps are:\n\n1. Connect to the server on port 8006. Push past any SSL/TLS security alert and login to the Proxmox-VE server GUI as the root user.\n2. Switch to \"Server View\"\u0026nbsp;\u003c!--A--\u003e\u0026#x1F130; then expand the \"Datacenter\"\u0026nbsp;\u003c!--B--\u003e\u0026#x1F131; and click on the name of your server\u0026nbsp;\u003c!--C--\u003e\u0026#x1F132; to select it.\n3. In the middle panel, find the \"System\" group\u0026nbsp;\u003c!--D--\u003e\u0026#x1F133; and, if necessary, click on its title to expand the group.\n4. Click \"Certificates\"\u0026nbsp;\u003c!--E--\u003e\u0026#x1F134;. The right hand panel changes to show you the certificate and key which are generated automatically by Proxmox-VE.\n5. Click the \u003ckbd\u003eUpload\u0026nbsp;Custom\u0026nbsp;Certificate\u003c/kbd\u003e button\u0026nbsp;\u003c!--F--\u003e\u0026#x1F135;. This opens a dialog box.\n6. Click the upper \u003ckbd\u003eFrom\u0026nbsp;File\u003c/kbd\u003e button\u0026nbsp;\u003c!--G--\u003e\u0026#x1F136;. This opens a file-selection picker (not shown). Navigate to the directory where your server's private key and certificate are stored, select the server's private key, and click the \u003ckbd\u003eUpload\u003c/kbd\u003e button. For example:\n\n\t```\n\t«path»/your.home.arpa/servers/lepton/lepton.key\n\t``` \n\n7. Repeat the process with the lower \u003ckbd\u003eFrom\u0026nbsp;File\u003c/kbd\u003e button\u0026nbsp;\u003c!--H--\u003e\u0026#x1F137;, this time selecting your server's certificate. For example:\n\n\t```\n\t«path»/your.home.arpa/servers/lepton/lepton.crt\n\t```\n\n8. Click the \u003ckbd\u003eUpload\u003c/kbd\u003e button\u0026nbsp;\u003c!--I--\u003e\u0026#x1F138;.\n\nThe Proxmox interface will restart and you may need to reload your browser window. Providing your browser has access to your domain certificate, you should be able to connect to your Proxmox-VE server without facing any \"This Connection Is Not Private\" alerts.\n\n\u003ca name=\"installAndroid\"\u003e\u003c/a\u003e\n### Android\n\nI'm sorry but I don't have any Android devices so I can't run tests and create screen shots or instructions. Please see:\n\n* [How to import a self-signed CA into an Android mobile device](https://support.sophos.com/support/s/article/KBA-000004715?language=en_US) starting at \"Importing the certificate\".\n* [How to install a Securly SSL certificate on Android device](https://support.securly.com/hc/en-us/articles/212869927-How-do-I-install-Securly-SSL-certificate-on-Android-device).\n\nIf those do not help then please try Googling for your specific device together with words like \"import SSL certificate\".\n\nNote that some (but not all) instructions call for conversion of the certificate to `.der` format. The lack of consistency on this question makes me wonder whether format conversion is actually necessary. Nevertheless, this conversion is something the `make_domain_certificate.sh` script does this for you. See [Figure 3](#figure3).\n\n\u003ca name=\"installWindows\"\u003e\u003c/a\u003e\n### Windows\n\nI'm sorry but I don't have any computers running Windows so I can't run tests and create screen shots or instructions. Please try the following links:\n\n- via GUI: [Importing a Self-Signed Certificate Authority into Windows](https://docs.progress.com/bundle/marklogic-server-secure-11/page/topics/configuring-ssl-on-app-servers/accessing-an-ssl-enabled-server-from-a-browser-or-webdav-client/importing-a-self-signed-certificate-authority-into-windows.html)\n\n- via CLI: [GitHub repo](https://github.com/millermatt/osca). In this case, I *suspect* that the Windows behaviour is similar to that of macOS in that the \"Copy new certs here\" column is slightly misleading. I *think* you probably just run:\n\n\t``` console\n\tcertutil -addstore -f \"Root\" \u003cpath_to_cert\u003e\n\t```\n\n\tand the *result* of that command is that the domain certificate winds up in the store at:\n\n\t```\n\tC:\\Windows\\System32\\certsrv\\CertEnroll\\\n\t```\n\nIf those links don't help then Google is your friend.\n\n\u003chr\u003e\n\n\u003ca name=\"usefulCommands\"\u003e\u003c/a\u003e\n## useful commands\n\n1. Display a certificate:\n\n\t``` console\n\t$ openssl x509 -noout -text -in «file»\n\t```\n\n\twhere `«file»` is one of the following examples:\n\n\t* `your.home.arpa/CA/your.home.arpa.crt` (domain certificate)\n\t* `your.home.arpa/servers/lepton/lepton.crt` (server certificate)\n\t* `your.home.arpa/wildcard/your.home.arpa.crt` (wildcard certificate)\n\n2. Extract the public key from a private key:\n\n\t``` console\n\t$ openssl rsa -pubout -in «file»\n\t```\n\n\twhere `«file»` is one of the following examples:\n\n\t* `your.home.arpa/CA/your.home.arpa.key` (CA private key)\n\t* `your.home.arpa/servers/lepton/lepton.key` (server private key)\n\t* `your.home.arpa/wildcard/your.home.arpa.key` (wildcard private key)\n\n3. Extract the public key from a certificate:\n\n\t``` console\n\t$ openssl x509 -pubkey -noout -in «file»\n\t```\n\n\twhere `«file»` is one of the following examples:\n\n\t* `your.home.arpa/CA/your.home.arpa.crt` (domain certificate)\n\t* `your.home.arpa/servers/lepton/lepton.crt` (server certificate)\n\t* `your.home.arpa/wildcard/your.home.arpa.crt` (wildcard certificate)\n\n4. Extract the subject from a certificate:\n\n\t* for a domain certificate:\n\n\t\t``` console\n\t\t$ openssl x509 -subject -noout -in «file» \n\t\t```\n\n\t* for a server certificate:\n\n\t\t``` console\n\t\t$ openssl x509 -subject -ext subjectAltName -noout -in «file» \n\t\t```\n\n\twhere `«file»` is one of the following examples:\n\n\t* `your.home.arpa/CA/your.home.arpa.crt` (domain certificate)\n\t* `your.home.arpa/servers/lepton/lepton.crt` (server certificate)\n\t* `your.home.arpa/wildcard/your.home.arpa.crt` (wildcard certificate)\n\n\tRecall that a [*certificate*](#onCerts) binds a public key with a subject and signs the pairing with a private key. This command extracts the subject and, if present, any alternative names for the subject; the previous command extracts the public key.\n\n5. Verify a domain certificate:\n\n\t``` console\n\t$ openssl verify -show_chain -CAfile «domainCert» «domainCert»\n\t```\n\n\twhere:\n\n\t* `«domainCert»` is the path to your domain certificate, such as:\n\n\t\t```\n\t\tyour.home.arpa/CA/your.home.arpa.crt\n\t\t```\n\n\tPassing the same path to this command, twice, makes sense when you think about it. Unlike server or wildcard certificates which are signed by your CA, your domain certificate is \"self-signed\" so it should also be \"self-verified\".\n\n6. Verify a server or wildcard certificate chain offline:\n\n\t``` console\n\t$ openssl verify -show_chain -CAfile «domainCert» «targetCert»\n\t```\n\n\twhere:\n\n\t* `«domainCert»` is the path to your domain certificate, such as:\n\n\t\t```\n\t\tyour.home.arpa/CA/your.home.arpa.crt\n\t\t```\n\n\t* `«targetCert»` is the path to your server or wildcard certificate, such as:\n\n\t\t* `your.home.arpa/servers/lepton/lepton.crt` (server certificate)\n\t\t* `your.home.arpa/wildcard/your.home.arpa.crt` (wildcard certificate)\n\n\tNote that this command will always report that your server certificate is untrusted, which is correct. The trust of a server certificate is *implied* when you install the domain certificate in a place where your **browser** (not this `openssl` command) can see it **and** affirm that you trust the domain certificate.\n\n7. Verify a server or wildcard certificate chain online:\n\n\t``` console\n\t$ echo -n | \\\n\t  openssl s_client -showcerts \\\n\t    -CAfile «domainCert» \\\n\t    -connect «HOST»:«PORT»\n\t```\n\n\twhere:\n\n\t* `«domainCert»` is the path to your domain certificate, such as:\n\n\t\t```\n\t\tyour.home.arpa/CA/your.home.arpa.crt\n\t\t```\n\n\t* `«HOST»` is any mechanism for reaching your target, such as the server's:\n\n\t\t- fully-qualified domain name (eg `boson.your.home.arpa`)\n\t\t- host name (eg `boson`)\n\t\t- IP address (eg `192.168.203.102`)\n\t\t- multicast domain name (eg `boson.local`)\n\n\tNotes:\n\n\t1. The host name and IP address forms will complain about \"Can't use SSL_get_servername\" but this is normal and should be ignored.\n\t2. The `echo -n` piped to `openssl` stops the command from hanging. If you omit that element, you will need to press \u003ckbd\u003econtrol\u003c/kbd\u003e+\u003ckbd\u003ed\u003c/kbd\u003e.\n\t3. Do **not** include a protocol indicator like \"http\" or \"https\" after the `-connect`:\n\n\t\t- right: `-connect boson.your.home.arpa:3000`\n\t\t- **wrong:** `-connect https://boson.your.home.arpa:3000`\n\n\t4. When probing your reverse-proxy host, use port 443, not 80.\n\n8. Display a certificate signing request:\n\n\t``` console\n\t$ openssl req -noout -text -in «file»\n\t```\n\n\twhere `«file»` is one of the following examples:\n\n\t* `your.home.arpa/servers/lepton/lepton.csr` (server request)\n\t* `your.home.arpa/wildcard/your.home.arpa.csr` (wildcard request)\n\n9. Convert a certificate in PEM format to DER format:\n\n\t``` console\n\t$ openssl x509 -inform pem -in «infile» -outform der -out «outfile»\n\t```\n\n\twhere `«infile»` is one of the following examples:\n\n\t* `your.home.arpa/CA/your.home.arpa.crt` (domain certificate)\n\t* `your.home.arpa/servers/lepton/lepton.crt` (server certificate)\n\n\tand `«outfile»` should be the same as `«infile»` but replacing the `.crt` or `.pem` extension with `.der`, as in:\n\n\t* `your.home.arpa/CA/your.home.arpa.der` (domain certificate)\n\t* `your.home.arpa/servers/lepton/lepton.der` (server certificate)\n\n\tNote that `make_domain_certificate.sh` already generates a DER-format domain certificate for you as a convenience. Proxmox-VE servers expect and accept PEM-format server certificates so there really should not be any need to convert those.\n\n10. Rebuild the `/etc/ssl/certs` structure:\n\n\t```\n\t$ sudo update-ca-certificates --fresh\n\t```\n\n\u003ca name=\"seeAlso\"\u003e\u003c/a\u003e\n## see also\n\n* [OpenSSL Documentation](https://docs.openssl.org/3.0/man1/)\n* [GoLinuxCloud OpenSSL Cheat Sheet](https://www.golinuxcloud.com/openssl-cheatsheet/) - this site is **excellent.**\n* [SSL.com Knowledgebase](https://www.ssl.com/info/)\n* Nginx:\n\t* [home page](https://nginx.org/en/) \n\t* [documentation](https://docs.nginx.com)\n\t* [Docker container implementation](https://nginxproxymanager.com)\n\n\u003ca name=\"acknowledgement\"\u003e\u003c/a\u003e\n## acknowledgement\n\nAlthough the scripts in this repository only bear a passing resemblance to the script at the URL below, it did have a significant effect on my approach to the problem so I'm citing it as a source:\n\n* [`provision-pveproxy-certificate.sh`](https://github.com/rgl/proxmox-ve/blob/master/example/provision-pveproxy-certificate.sh) from [github.com/rgl/proxmox-ve](https://github.com/rgl/proxmox-ve)\n\nThere is nothing wrong with that script but I found that the self-signed server certificates it produces would not work properly on iOS. They were not \"root certificates\" and trust could not be enabled, which kinda defeats the purpose.\n\n\u003ca name=\"history\"\u003e\u003c/a\u003e\n## history\n\nThe first few editions of this repository were solely focused on generating a self-signed root certificate for Proxmox-VE servers. Later, when I started to focus on Nginx, I realised that I only lacked the capability to generate wildcard certificates, thereby turning this repository into a more general solution for producing self-signed certificates for home networks.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fparaphraser%2Fssl-certificates","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fparaphraser%2Fssl-certificates","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fparaphraser%2Fssl-certificates/lists"}