43

I have my own domain with web services written in Go. I am using the inbuilt Go web server, without Nginx or Apache in front.

I would like to start serving over HTTPS and I realized Let's Encrypt is just about to become THE WAY for doing that.

Can anyone share the whole setup procedure for configuring a Go app running on a Linux server?

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
Daniele B
  • 19,801
  • 29
  • 115
  • 173
  • 5
    Use https://caddyserver.com/ - which does this for you (issues a certificate & proxies to your application). – elithrar May 19 '16 at 12:39
  • 2
    What is the question : how to create a certificate for a Go webserver ? How to configure the Go webserver to use the certificate/private key ? Other ? – T. Claverie May 19 '16 at 16:41
  • Go's own `crypto/tls` package is able to load certificates (public and private keys) from PEM-formatted files. A tool provided by Let's Encrypt (now it appears to be rather promoted by EFF) is able to provide you with that PEM-formatted file. So: 1) Read a guide on obtaining the certificate from Let's Encrypt and do that; 2) Once you have that certificate, read up on how to make `crypto/tls` read it and implement that in your servers; 3) Have your servers read your cert. You're done. – kostix May 19 '16 at 17:25
  • To say that in other way, there's no way of "configuring" your servers unless you already *implemented* configuration knobs in them to do that. Since you most probably have not yet, you might do something like making them parse a special command-line option specifying the pathname of the certificate, say, `-cert`. – kostix May 19 '16 at 17:33
  • **Related info**: redirecting all http traffic to https. https://stackoverflow.com/questions/37536006/how-do-i-rewrite-redirect-from-http-to-https-in-go – Benny Jobigan Sep 10 '17 at 08:56

3 Answers3

82

This is the minimal automatic setup of an HTTPS server using Go and Let's Encrypt certificates I have found:

package main

import (
    "crypto/tls"
    "log"
    "net/http"

    "golang.org/x/crypto/acme/autocert"
)

func main() {
    certManager := autocert.Manager{
        Prompt:     autocert.AcceptTOS,
        HostPolicy: autocert.HostWhitelist("example.com"), //Your domain here
        Cache:      autocert.DirCache("certs"),            //Folder for storing certificates
    }

    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello world"))
    })

    server := &http.Server{
        Addr: ":https",
        TLSConfig: &tls.Config{
            GetCertificate: certManager.GetCertificate,
            MinVersion: tls.VersionTLS12, // improves cert reputation score at https://www.ssllabs.com/ssltest/
        },
    }

    go http.ListenAndServe(":http", certManager.HTTPHandler(nil))

    log.Fatal(server.ListenAndServeTLS("", "")) //Key and cert are coming from Let's Encrypt
}

More information on the autocert package: link

EDIT: Needed to make http available because of letsencrypt security issue, read more here. As a bonus of this fix we now have http-->https redirect. The old example will continue to work if you have already received certificates on it, but it will break for new sites.

Scott Stensland
  • 26,870
  • 12
  • 93
  • 104
Pylinux
  • 11,278
  • 4
  • 60
  • 67
  • Thanks, it is the example I was looking for! But one hast to be careful according to the documentation: *This package is a work in progress and makes no API stability promises.* – oliverpool Dec 27 '16 at 10:30
  • Thank you very much! This really should be selected as the correct answer. – Chris Jan 04 '17 at 01:38
  • This does not include the .well-known folder, does it? So this snippet only includes using the certificates, not renewing them? – James Cameron Jan 30 '17 at 10:07
  • 3
    No this is the complete package. If you run this code you'll see that it creates a valid certificate. And since it can create it can also update. The server I'm running this on has updated the certificate at least once. – Pylinux Jan 31 '17 at 11:16
  • @JamesCameron It uses the TLS-SNI challenge to provision the certificate not the HTTP. – Chris Powell Mar 22 '17 at 18:40
  • 1
    I get those two handshake errors and I can't figure out what the problem is: "acme/autocert: missing server name" and "acme/autocert: host not configured". Can someone help me please? – Marco M Mar 31 '17 at 16:42
  • @MarcoM Create a new stackoverflow question where you paste your code and I'll have a look at it. – Pylinux Apr 01 '17 at 18:11
  • @Pylinux Question created: http://stackoverflow.com/questions/43161559/setting-up-lets-encrypt-with-go-handshake-errors – Marco M Apr 01 '17 at 20:17
  • There is no such method certManager.HTTPHandler(nil) – Pushan Nov 14 '18 at 14:13
  • 1
    @Pushan What version of `golang.org/x/crypto/acme/autocert` are you using? Because in master it seems like the function is there: [link](https://github.com/golang/crypto/blob/master/acme/autocert/autocert_test.go#L672) – Pylinux Nov 16 '18 at 10:29
  • Why doesn't this need an LE account? – andig Mar 14 '21 at 13:28
37

I found a very simple solution, using the standalone mode.


INSTALL THE CERTBOT CLIENT (recommended by Let's Encrypt)

(go to the directory where you want to install the certbot client)
git clone https://github.com/certbot/certbot
cd certbot
./certbot-auto --help`


ISSUE CERTIFICATE (FIRST TIME)

N.B. this operation happens through the port 80, so in case your Go app listens on port 80, it needs to be switched off before running this command (which is very quick to run, by the way)

./certbot-auto certonly --standalone-supported-challenges http-01 -d www.yourdomain.com

ADD SSL LISTENER IN YOUR GO CODE

http.ListenAndServeTLS(":443", "/etc/letsencrypt/live/www.yourdomain.com/fullchain.pem", "/etc/letsencrypt/live/www.yourdomain.com/privkey.pem", nil)

Done!


TO RENEW CERTIFICATE (certificates expire after 90 days)

N.B. You can either run this manually (you will receive an email several days before the certificate expires), or set up a crontab

if your Go app doesn't listen to port 80 anymore, your Go app can keep running while you execute this command:
./certbot-auto renew --standalone

if your Go app still listens to port 80, you can specify the commands to stop and restart the Go app:
./certbot-auto renew --standalone --pre-hook "command to stop Go app" --post-hook "command to start Go app"

for the complete documentation of the Certbot commands: https://certbot.eff.org/docs/using.html

Daniele B
  • 19,801
  • 29
  • 115
  • 173
0

If you can use DNS verification, that's the way to go for renewals.

For using the certificate, simple do:

    c := &tls.Config{MinVersion: tls.VersionTLS12}
    s := &http.Server{Addr: ":443", Handler: Gzipler(nosurf.New(router), 1), TLSConfig: c}
    log.Fatal(s.ListenAndServeTLS(
        "/etc/letsencrypt/live/XXX/fullchain.pem", 
        "/etc/letsencrypt/live/XXX/privkey.pem"
    ))

This one has Gzip & CSRF protection included. You can use

Handler: router

without those extra features.

Jiulin Teng
  • 299
  • 3
  • 8