diff --git a/README.md b/README.md
index 8fc253a9c1bd5ae1b43e27c1a84ad05b336ef19c..fd2d0dab2b50ff1975a1cac0b138b0d1c43b4155 100644
--- a/README.md
+++ b/README.md
@@ -52,9 +52,6 @@ understands the following attributes:
   * `auth_key`: a long-term key to authenticate HTTP-based cookies
   * `geo_ip_data_files`: GeoIP databases to use (in mmdb format), if
     unset the module will use the default GeoLite2-Country db
-  * `trusted_forwarders`: list of trusted IP addresses (reverse
-    proxies). If a request comes from here, we will use the
-    *remote_addr_header* instead
   * `remote_addr_header`: HTTP header to use to obtain the remote
     client address, when the request comes from a trusted forwarder
 * `keystore`: configures the connection to the keystore service
@@ -73,6 +70,10 @@ understands the following attributes:
       * `path` is a regular expression to match the request URL path
       * `cn` is a regular expression that must match the CommonName
         part of the subject of the client certificate
+  * `trusted_forwarders`: list of trusted IP addresses (reverse
+    proxies). If a request comes from here, we will trust the
+    X-Forwarded-Proto and X-Real-IP headers when determining the
+    client IP address
 
 ## Device tracking
 
diff --git a/cmd/sso-server/main_test.go b/cmd/sso-server/main_test.go
index 1e26c609d3dd282f64075e5ed05682bea1af1e56..cdbd8b9ad2382171209273db4d6778ec68793b62 100644
--- a/cmd/sso-server/main_test.go
+++ b/cmd/sso-server/main_test.go
@@ -31,8 +31,6 @@ csrf_secret: "XLFtiymBU5p59K/IsqW/oh/5dfP4UC6JSNWMVeiQ8t8GjnB1rzusIFnyho5y4nE1"
 auth_service: sso
 device_manager:
   auth_key: "ffolt81h4CA5kEcwckXmuUUkchwKQmRAeWb1H6Kpzx3+uGqwrVpBfGwzRSYaeir1"
-  trusted_forwarders:
-    - 192.168.10.10
 `
 
 func TestMain_LoadConfig(t *testing.T) {
diff --git a/server/device/manager.go b/server/device/manager.go
index 8c3e1063b2d47f01009cef7514fb92a05ce0bc2d..a9dfe231c1330997a2dad62e4910b7a0cd04d214 100644
--- a/server/device/manager.go
+++ b/server/device/manager.go
@@ -24,18 +24,14 @@ func randomDeviceID() string {
 
 // Manager can provide DeviceInfo entries for incoming HTTP requests.
 type Manager struct {
-	store             sessions.Store
-	geodb             *geoIPDb
-	trustedForwarders []net.IPNet
-	remoteAddrHeader  string
+	store sessions.Store
+	geodb *geoIPDb
 }
 
 // Config stores options for the device info manager.
 type Config struct {
-	AuthKey           string   `yaml:"auth_key"`
-	GeoIPDataFiles    []string `yaml:"geo_ip_data_files"`
-	TrustedForwarders []string `yaml:"trusted_forwarders"`
-	RemoteAddrHeader  string   `yaml:"remote_addr_header"`
+	AuthKey        string   `yaml:"auth_key"`
+	GeoIPDataFiles []string `yaml:"geo_ip_data_files"`
 }
 
 // New returns a new Manager with the given configuration.
@@ -43,27 +39,15 @@ func New(config *Config) (*Manager, error) {
 	if config == nil {
 		config = &Config{}
 	}
-	tf, err := parseIPNetList(config.TrustedForwarders)
-	if err != nil {
-		return nil, err
-	}
 
-	var geodb *geoIPDb
-	if geodb, err = newGeoIP(config.GeoIPDataFiles); err != nil {
+	geodb, err := newGeoIP(config.GeoIPDataFiles)
+	if err != nil {
 		log.Printf("Warning: GeoIP disabled: %v", err)
 	}
 
-	// The remote IP header (if any) defaults to X-Forwarded-For.
-	hdr := "X-Forwarded-For"
-	if config.RemoteAddrHeader != "" {
-		hdr = config.RemoteAddrHeader
-	}
-
 	return &Manager{
-		geodb:             geodb,
-		store:             newStore([]byte(config.AuthKey)),
-		trustedForwarders: tf,
-		remoteAddrHeader:  hdr,
+		geodb: geodb,
+		store: newStore([]byte(config.AuthKey)),
 	}, nil
 }
 
@@ -80,7 +64,11 @@ func (m *Manager) GetDeviceInfoFromRequest(w http.ResponseWriter, req *http.Requ
 		// Generate a new Device ID and save it on the client.
 		devID = randomDeviceID()
 		session.Values["id"] = devID
-		session.Save(req, w)
+		if err := session.Save(req, w); err != nil {
+			// This is likely a misconfiguration issue, so
+			// we want to know about it.
+			log.Printf("error saving device manager session: %v", err)
+		}
 	}
 
 	uaStr := req.UserAgent()
@@ -100,7 +88,7 @@ func (m *Manager) GetDeviceInfoFromRequest(w http.ResponseWriter, req *http.Requ
 		d.RemoteZone = "onion"
 	} else {
 		// If we have an IP address, we can add more information.
-		if ip := m.getIPFromRequest(req); ip != nil {
+		if ip := getIPFromRequest(req); ip != nil {
 			d.RemoteAddr = ip.String()
 
 			// Country lookup.
@@ -114,3 +102,12 @@ func (m *Manager) GetDeviceInfoFromRequest(w http.ResponseWriter, req *http.Requ
 
 	return &d
 }
+
+func getIPFromRequest(req *http.Request) net.IP {
+	// Parse the RemoteAddr Request field, for starters.
+	host, _, err := net.SplitHostPort(req.RemoteAddr)
+	if err != nil {
+		host = req.RemoteAddr
+	}
+	return net.ParseIP(host)
+}
diff --git a/vendor/git.autistici.org/ai3/go-common/serverutil/http.go b/vendor/git.autistici.org/ai3/go-common/serverutil/http.go
index 7797ae66014d20d00625e91639f9a727a44c3e39..84faef94bd5ea0a8cdbcee63108186e91bc6b0c7 100644
--- a/vendor/git.autistici.org/ai3/go-common/serverutil/http.go
+++ b/vendor/git.autistici.org/ai3/go-common/serverutil/http.go
@@ -20,43 +20,63 @@ var gracefulShutdownTimeout = 3 * time.Second
 type ServerConfig struct {
 	TLS                 *TLSServerConfig `yaml:"tls"`
 	MaxInflightRequests int              `yaml:"max_inflight_requests"`
+	TrustedForwarders   []string         `yaml:"trusted_forwarders"`
 }
 
-// Serve HTTP(S) content on the specified address. If serverConfig is
-// not nil, enable HTTPS and TLS authentication.
-//
-// This function will return an error if there are problems creating
-// the listener, otherwise it will handle graceful termination on
-// SIGINT or SIGTERM and return nil.
-func Serve(h http.Handler, serverConfig *ServerConfig, addr string) (err error) {
+func (config *ServerConfig) buildHTTPServer(h http.Handler, addr string) (*http.Server, error) {
 	var tlsConfig *tls.Config
-	if serverConfig != nil {
-		if serverConfig.TLS != nil {
-			tlsConfig, err = serverConfig.TLS.TLSConfig()
+	var err error
+	if config != nil {
+		if config.TLS != nil {
+			tlsConfig, err = config.TLS.TLSConfig()
 			if err != nil {
-				return err
+				return nil, err
 			}
-			h, err = serverConfig.TLS.TLSAuthWrapper(h)
+			h, err = config.TLS.TLSAuthWrapper(h)
+			if err != nil {
+				return nil, err
+			}
+		}
+
+		// If TrustedForwarders is defined, rewrite the request
+		// headers using X-Forwarded-Proto and X-Real-IP.
+		if len(config.TrustedForwarders) > 0 {
+			h, err = newProxyHeaders(h, config.TrustedForwarders)
 			if err != nil {
-				return err
+				return nil, err
 			}
 		}
 
-		if serverConfig.MaxInflightRequests > 0 {
-			h = newLoadSheddingWrapper(serverConfig.MaxInflightRequests, h)
+		// If MaxInflightRequests is set, enable the load
+		// shedding wrapper.
+		if config.MaxInflightRequests > 0 {
+			h = newLoadSheddingWrapper(config.MaxInflightRequests, h)
 		}
 	}
 
 	// These are not meant to be external-facing servers, so we
 	// can be generous with the timeouts to keep the number of
 	// reconnections low.
-	srv := &http.Server{
+	return &http.Server{
 		Addr:         addr,
 		Handler:      instrumentHandler(h),
 		ReadTimeout:  30 * time.Second,
 		WriteTimeout: 30 * time.Second,
 		IdleTimeout:  600 * time.Second,
 		TLSConfig:    tlsConfig,
+	}, nil
+}
+
+// Serve HTTP(S) content on the specified address. If config.TLS is
+// not nil, enable HTTPS and TLS authentication.
+//
+// This function will return an error if there are problems creating
+// the listener, otherwise it will handle graceful termination on
+// SIGINT or SIGTERM and return nil.
+func Serve(h http.Handler, config *ServerConfig, addr string) error {
+	srv, err := config.buildHTTPServer(h, addr)
+	if err != nil {
+		return err
 	}
 
 	// Install a signal handler for gentle process termination.
@@ -80,7 +100,7 @@ func Serve(h http.Handler, serverConfig *ServerConfig, addr string) (err error)
 	}()
 	signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
 
-	if tlsConfig != nil {
+	if srv.TLSConfig != nil {
 		err = srv.ListenAndServeTLS("", "")
 	} else {
 		err = srv.ListenAndServe()
diff --git a/vendor/git.autistici.org/ai3/go-common/serverutil/json.go b/vendor/git.autistici.org/ai3/go-common/serverutil/json.go
index b307932eb878197d49c945e51e5f42adb240747e..746ed201eff520118640e881137bb86c38c427aa 100644
--- a/vendor/git.autistici.org/ai3/go-common/serverutil/json.go
+++ b/vendor/git.autistici.org/ai3/go-common/serverutil/json.go
@@ -2,6 +2,7 @@ package serverutil
 
 import (
 	"encoding/json"
+	"log"
 	"net/http"
 )
 
@@ -28,10 +29,19 @@ func DecodeJSONRequest(w http.ResponseWriter, r *http.Request, obj interface{})
 
 // EncodeJSONResponse writes an application/json response to w.
 func EncodeJSONResponse(w http.ResponseWriter, obj interface{}) {
+	data, err := json.Marshal(obj)
+	if err != nil {
+		log.Printf("JSON serialization error: %v", err)
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+		return
+	}
+
 	w.Header().Set("Content-Type", "application/json")
 	w.Header().Set("Pragma", "no-cache")
 	w.Header().Set("Cache-Control", "no-store")
 	w.Header().Set("Expires", "-1")
 	w.Header().Set("X-Content-Type-Options", "nosniff")
-	_ = json.NewEncoder(w).Encode(obj)
+	if _, err = w.Write(data); err != nil {
+		log.Printf("error writing response: %v", err)
+	}
 }
diff --git a/server/device/addr.go b/vendor/git.autistici.org/ai3/go-common/serverutil/proxy_headers.go
similarity index 59%
rename from server/device/addr.go
rename to vendor/git.autistici.org/ai3/go-common/serverutil/proxy_headers.go
index 0d864e57f02b8033aa44dc6a885f2dc7fa3e5eb7..00480b93ec3fb54b4843c7cc8709cf750591f363 100644
--- a/server/device/addr.go
+++ b/vendor/git.autistici.org/ai3/go-common/serverutil/proxy_headers.go
@@ -1,37 +1,41 @@
-package device
+package serverutil
 
 import (
 	"fmt"
 	"net"
 	"net/http"
-	"strings"
+
+	"github.com/gorilla/handlers"
 )
 
-func (m *Manager) getIPFromRequest(req *http.Request) net.IP {
-	// Parse the RemoteAddr Request field, for starters.
-	host, _, err := net.SplitHostPort(req.RemoteAddr)
+type proxyHeaders struct {
+	wrap, phWrap http.Handler
+	forwarders   []net.IPNet
+}
+
+func newProxyHeaders(h http.Handler, trustedForwarders []string) (http.Handler, error) {
+	f, err := parseIPNetList(trustedForwarders)
 	if err != nil {
-		host = req.RemoteAddr
-	}
-	ip := net.ParseIP(host)
-	if ip == nil {
-		return nil
+		return nil, err
 	}
+	return &proxyHeaders{
+		wrap:       h,
+		phWrap:     handlers.ProxyHeaders(h),
+		forwarders: f,
+	}, nil
+}
 
-	// See if it's a trusted forwarder, in which case go for the
-	// X-Forwarded-For header.
-	if matchIPNetList(ip, m.trustedForwarders) {
-		fwdAddr := req.Header.Get(m.remoteAddrHeader)
-		if fwdAddr == "" {
-			return nil
-		}
-		ip = net.ParseIP(strings.Split(fwdAddr, ", ")[0])
-		if ip == nil {
-			return nil
-		}
+func (p *proxyHeaders) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	host, _, err := net.SplitHostPort(r.RemoteAddr)
+	if err != nil {
+		host = r.RemoteAddr
 	}
-
-	return ip
+	ip := net.ParseIP(host)
+	if ip != nil && matchIPNetList(ip, p.forwarders) {
+		p.phWrap.ServeHTTP(w, r)
+		return
+	}
+	p.wrap.ServeHTTP(w, r)
 }
 
 func fullMask(ip net.IP) net.IPMask {
diff --git a/vendor/vendor.json b/vendor/vendor.json
index 661846d12788c5d868f6abc5de49de99509f2474..ff06795b7109bb02a42b21b0487c2726acbb3571 100644
--- a/vendor/vendor.json
+++ b/vendor/vendor.json
@@ -15,10 +15,10 @@
 			"revisionTime": "2017-12-17T20:32:41Z"
 		},
 		{
-			"checksumSHA1": "nlGRxexjZUxnHc/z/+ZqV/Xq51w=",
+			"checksumSHA1": "WZkkasKZTt4UoJHphxXqZauBGLk=",
 			"path": "git.autistici.org/ai3/go-common/serverutil",
-			"revision": "aa88011352b67032c19b0c14eaa06b3417176753",
-			"revisionTime": "2017-12-19T17:20:40Z"
+			"revision": "be4da336395850dd80cfaf36bfa0e364ec22a30f",
+			"revisionTime": "2018-01-14T09:57:18Z"
 		},
 		{
 			"checksumSHA1": "C1BVHHj8iBgBN5EWr3ucsGoEzew=",