diff --git a/README.md b/README.md
index 799ba9cfce08eec9a6c04ec054be9d98856c8a22..e9c26bd1eb1479c32440a4c248548f0c295baa8a 100644
--- a/README.md
+++ b/README.md
@@ -95,6 +95,7 @@ Configuration is divided into different sections: `server`, `auth`, `ssh`, and `
 - `tls_cert` : string. Path to the TLS cert.
 - `address` : string. IP address to listen on. If unset the server listens on all addresses.
 - `port` : int. Port to listen on.
+- `user` : string. User to which the server drops privileges to.
 - `cookie_secret`: string. Authentication key for the session cookie.
 - `http_logfile`: string. Path to the HTTP request log. Logs are written in the [Common Log Format](https://en.wikipedia.org/wiki/Common_Log_Format). If not set logs are written to stderr.
 - `datastore`: string. Datastore connection string. See [Datastore](#datastore).
diff --git a/cmd/cashierd/main.go b/cmd/cashierd/main.go
index e82cbc7a03509a6f3173744e41fa0ff863d0f6a4..e16d20b35a264c39ec5837b2c0e85cf24c6ad63d 100644
--- a/cmd/cashierd/main.go
+++ b/cmd/cashierd/main.go
@@ -2,6 +2,7 @@ package main
 
 import (
 	"crypto/rand"
+	"crypto/tls"
 	"encoding/hex"
 	"encoding/json"
 	"errors"
@@ -11,6 +12,7 @@ import (
 	"io"
 	"io/ioutil"
 	"log"
+	"net"
 	"net/http"
 	"os"
 	"strings"
@@ -32,6 +34,7 @@ import (
 	"github.com/nsheridan/cashier/server/static"
 	"github.com/nsheridan/cashier/server/store"
 	"github.com/nsheridan/cashier/server/templates"
+	"github.com/sid77/drop"
 )
 
 var (
@@ -310,17 +313,50 @@ func certStore(config string) (store.CertStorer, error) {
 }
 
 func main() {
+	// Privileged section
 	flag.Parse()
 	config, err := readConfig(*cfg)
 	if err != nil {
 		log.Fatal(err)
 	}
+
 	fs.Register(config.AWS)
 	signer, err := signer.New(config.SSH)
 	if err != nil {
 		log.Fatal(err)
 	}
 
+	logfile := os.Stderr
+	if config.Server.HTTPLogFile != "" {
+		logfile, err = os.OpenFile(config.Server.HTTPLogFile, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0640)
+		if err != nil {
+			log.Fatal(err)
+		}
+	}
+
+	l, err := net.Listen("tcp", fmt.Sprintf("%s:%d", config.Server.Addr, config.Server.Port))
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	tlsConfig := &tls.Config{}
+	if config.Server.UseTLS {
+		tlsConfig.Certificates = make([]tls.Certificate, 1)
+		tlsConfig.Certificates[0], err = tls.LoadX509KeyPair(config.Server.TLSCert, config.Server.TLSKey)
+		if err != nil {
+			log.Fatal(err)
+		}
+		l = tls.NewListener(l, tlsConfig)
+	}
+
+	if config.Server.User != "" {
+		log.Print("Dropping privileges...")
+		if err = drop.DropPrivileges(config.Server.User); err != nil {
+			log.Fatal(err)
+		}
+	}
+
+	// Unprivileged section
 	var authprovider auth.Provider
 	switch config.Auth.Provider {
 	case "google":
@@ -361,19 +397,11 @@ func main() {
 	r.Methods("POST").Path("/admin/revoke").Handler(CSRF(appHandler{ctx, revokeCertHandler}))
 	r.Methods("GET").Path("/admin/certs").Handler(CSRF(appHandler{ctx, listAllCertsHandler}))
 	r.PathPrefix("/").Handler(http.FileServer(static.FS(false)))
-	logfile := os.Stderr
-	if config.Server.HTTPLogFile != "" {
-		logfile, err = os.OpenFile(config.Server.HTTPLogFile, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0660)
-		if err != nil {
-			log.Fatal(err)
-		}
-	}
 	h := handlers.LoggingHandler(logfile, r)
 
-	fmt.Println("Starting server...")
-	l := fmt.Sprintf("%s:%d", config.Server.Addr, config.Server.Port)
-	if config.Server.UseTLS {
-		log.Fatal(http.ListenAndServeTLS(l, config.Server.TLSCert, config.Server.TLSKey, h))
+	log.Print("Starting server...")
+	s := &http.Server{
+		Handler: h,
 	}
-	log.Fatal(http.ListenAndServe(l, h))
+	log.Fatal(s.Serve(l))
 }
diff --git a/example-server.conf b/example-server.conf
index 35a53d123bf46ea7a990bc4ba644abddceb30070..fcb6558eb0a54392bd5621e5176b055d9b313129 100644
--- a/example-server.conf
+++ b/example-server.conf
@@ -3,8 +3,9 @@ server {
   use_tls = true  # Optional. If this is set then `tls_key` and `tls_cert` must be set
   tls_key = "server.key"  # Path to TLS key
   tls_cert = "server.crt"  # Path to TLS certificate
+  address = "127.0.0.1"  # Optional. IP address to listen on
   port = 443  # Port to listen on
-  address = "127.0.0.1"  # Optional. IP address to listen on.
+  user = "www" # Optional. User to which the server drops privileges to
   cookie_secret = "supersecret"  # Authentication key for the client cookie
   csrf_secret = "supersecret"  # Authentication key for the CSRF token
   http_logfile = "http.log"  # Logfile for HTTP requests
@@ -28,7 +29,7 @@ ssh {
   signing_key = "signing_key"  # Path to the CA signing secret key
   additional_principals = ["ec2-user", "ubuntu"]  # Additional principals to allow
   max_age = "720h"  # Maximum lifetime of a ssh certificate
-  permissions = ["permit-pty", "permit-X11-forwarding", "permit-agent-forwarding", "permit-port-forwarding", "permit-user-rc"]  #  Permissions associated with a certificate.
+  permissions = ["permit-pty", "permit-X11-forwarding", "permit-agent-forwarding", "permit-port-forwarding", "permit-user-rc"]  #  Permissions associated with a certificate
 }
 
 # Optional AWS config. if an aws config is present, the signing key can be read from S3 using the syntax `/s3/bucket/path/to/signing.key`.
diff --git a/server/config/config.go b/server/config/config.go
index fb64f6cc59d3b100436bb8b398c01dd9f539c854..f1341c15113fcfa73aa0a771f3b1d6dca8e1b83b 100644
--- a/server/config/config.go
+++ b/server/config/config.go
@@ -31,6 +31,7 @@ type Server struct {
 	TLSCert      string `mapstructure:"tls_cert"`
 	Addr         string `mapstructure:"address"`
 	Port         int    `mapstructure:"port"`
+	User         string `mapstructure:"user"`
 	CookieSecret string `mapstructure:"cookie_secret"`
 	CSRFSecret   string `mapstructure:"csrf_secret"`
 	HTTPLogFile  string `mapstructure:"http_logfile"`
diff --git a/vendor/github.com/sid77/drop/LICENSE b/vendor/github.com/sid77/drop/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..37004bf22b6e137a831eb1731c30842304bafdfa
--- /dev/null
+++ b/vendor/github.com/sid77/drop/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2016 Marco Bonetti
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/github.com/sid77/drop/drop.go b/vendor/github.com/sid77/drop/drop.go
new file mode 100644
index 0000000000000000000000000000000000000000..0fb64a94884fdecbb8ec4f135c84c01c85021ee8
--- /dev/null
+++ b/vendor/github.com/sid77/drop/drop.go
@@ -0,0 +1,35 @@
+package drop
+
+import (
+	"os/user"
+	"strconv"
+
+	"github.com/sid77/drop/syscall"
+)
+
+func DropPrivileges(runAsUser string) (err error) {
+	usr, err := user.Lookup(runAsUser)
+	if err != nil {
+		return err
+	}
+
+	gid, err := strconv.Atoi(usr.Gid)
+	if err != nil {
+		return err
+	}
+
+	uid, err := strconv.Atoi(usr.Uid)
+	if err != nil {
+		return err
+	}
+
+	if err = syscall.Setgid(gid); err != nil {
+		return err
+	}
+
+	if err = syscall.Setuid(uid); err != nil {
+		return err
+	}
+
+	return nil
+}
diff --git a/vendor/github.com/sid77/drop/syscall/setre.go b/vendor/github.com/sid77/drop/syscall/setre.go
new file mode 100644
index 0000000000000000000000000000000000000000..ecf5ea9940c151cae1eff2780861bc2a526ec9fb
--- /dev/null
+++ b/vendor/github.com/sid77/drop/syscall/setre.go
@@ -0,0 +1,17 @@
+// +build !linux
+
+package syscall
+
+import (
+	"syscall"
+)
+
+func Setuid(uid int) error {
+	err := syscall.Setreuid(uid, uid)
+	return err
+}
+
+func Setgid(gid int) error {
+	err := syscall.Setregid(gid, gid)
+	return err
+}
diff --git a/vendor/github.com/sid77/drop/syscall/setres.go b/vendor/github.com/sid77/drop/syscall/setres.go
new file mode 100644
index 0000000000000000000000000000000000000000..afe43b9fec848636e6f41aa3385167d3538d1857
--- /dev/null
+++ b/vendor/github.com/sid77/drop/syscall/setres.go
@@ -0,0 +1,17 @@
+// +build linux
+
+package syscall
+
+import (
+	"syscall"
+)
+
+func Setuid(uid int) error {
+	err := syscall.Setresuid(uid, uid, uid)
+	return err
+}
+
+func Setgid(gid int) error {
+	err := syscall.Setresgid(gid, gid, gid)
+	return err
+}