Skip to content

Instantly share code, notes, and snippets.

@d-schmidt
Last active March 12, 2024 08:04
Show Gist options
  • Star 46 You must be signed in to star a gist
  • Fork 23 You must be signed in to fork a gist
  • Save d-schmidt/587ceec34ce1334a5e60 to your computer and use it in GitHub Desktop.
Save d-schmidt/587ceec34ce1334a5e60 to your computer and use it in GitHub Desktop.
How to redirect HTTP to HTTPS with a golang webserver.
package main
import (
"net/http"
"log"
)
func redirect(w http.ResponseWriter, req *http.Request) {
// remove/add not default ports from req.Host
target := "https://" + req.Host + req.URL.Path
if len(req.URL.RawQuery) > 0 {
target += "?" + req.URL.RawQuery
}
log.Printf("redirect to: %s", target)
http.Redirect(w, req, target,
// see comments below and consider the codes 308, 302, or 301
http.StatusTemporaryRedirect)
}
func index(w http.ResponseWriter, req *http.Request) {
// all calls to unknown url paths should return 404
if req.URL.Path != "/" {
log.Printf("404: %s", req.URL.String())
http.NotFound(w, req)
return
}
http.ServeFile(w, req, "index.html")
}
func main() {
// redirect every http request to https
go http.ListenAndServe(":80", http.HandlerFunc(redirect))
// serve index (and anything else) as https
mux := http.NewServeMux()
mux.HandleFunc("/", index)
http.ListenAndServeTLS(":443", "cert.pem", "key.pem", mux)
}
@d-schmidt
Copy link
Author

d-schmidt commented Oct 5, 2015

generate test tls certs with go: https://github.com/golang/go/blob/master/src/crypto/tls/generate_cert.go

generate_cert -host=127.0.0.1,localhost,example.com -ca -ecdsa-curve=P384

e.g. go run "$$(go env GOROOT)/src/crypto/tls/generate_cert.go"

@andreiavrammsd
Copy link

If you want to also send the request body (like for POST requests), you should use 307 code (http.StatusTemporaryRedirect).

@drstearns
Copy link

drstearns commented Jan 15, 2017

One thing to watch out for with this solution: if your HTTP server is running on a non-standard port (i.e., not 80), the req.Host value will be the host name and HTTP port number (e.g., example.com:8000). So you should strip off any port number that might be on req.Host so that the browser doesn't try to establish an HTTPS connection on the wrong port. You can do this using a line like:

host := strings.Split(req.Host, ":")[0]

And if your HTTPS server is running on a non-standard port, you need to append that to the host value so the browser knows which port to use.

It also might be slightly safer to use req.URL.Path for the rest of the URL path, rather than req.URL.String(). If the HTTP request included an absolute URL in the first line, req.URL.String() would return that entire URL, complete with scheme and host.

@d-schmidt
Copy link
Author

@andreiavrammsd I've changed the example to 307. A GET only server should use 301. Browsers will cache this redirect.
@drstearns using only req.URL.Path would omit query parameters. I've updated the example anyways.
I've added a comment for the possible port problem.

@d-schmidt
Copy link
Author

The docs do not recommend to use req.RequestURI. Never trust the client.
RFC 2616, Section 5.1.2: * is a valid RequestURI.
https://golang.org/pkg/net/http/#Request

// RequestURI is the unmodified Request-URI of the
// Request-Line (RFC 2616, Section 5.1) as sent by the client
// to a server. Usually the URL field should be used instead.

@AneudyM
Copy link

AneudyM commented Mar 30, 2018

You could also use the url package to construct the URL as well:

targetUrl := url.URL{ Scheme: "https", Host: r.Host, Path: r.URL.Path, RawQuery: r.URL.RawQuery, }

@MakotoE
Copy link

MakotoE commented Aug 15, 2019

Shouldn't you use http.StatusPermanentRedirect (308) as the status code for redirect, not http.StatusTemporaryRedirect (307), because you're probably not going to create an unsecure http route in the future?

@d-schmidt
Copy link
Author

d-schmidt commented Aug 18, 2019

@MakotoE you are probably right. I will add a another comment

@pystub
Copy link

pystub commented Nov 29, 2021

@drstearns it flops on ipv6 addresses.

This is what I came up with:

d := r.Host
if m, _ := regexp.MatchString(`:\d+$`, d); m {
	d = d[:strings.LastIndexByte(d, ':')]
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment