diff --git a/Dockerfile b/Dockerfile
index 273c1becea21a98d885d0e74c50b7c05b7b1f967..7910e514473620dcb1c19c51fa7ed254e8e9b9d9 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -3,7 +3,7 @@ LABEL maintainer="nsheridan@gmail.com"
 ARG SRC_DIR=/go/src/github.com/nsheridan/cashier
 WORKDIR ${SRC_DIR}
 ADD . ${SRC_DIR}
-RUN CGO_ENABLED=0 GOOS=linux go install -a -installsuffix static ./cmd/cashierd
+RUN CGO_ENABLED=0 GOOS=linux make install-cashierd
 
 FROM scratch
 LABEL maintainer="nsheridan@gmail.com"
diff --git a/Makefile b/Makefile
index 466d772dc70f7792bd7eeae4c5e5f043300aee71..934c79d92f8e369d48bf5aa94d939b14d275b301 100644
--- a/Makefile
+++ b/Makefile
@@ -1,8 +1,12 @@
 CASHIER_CMD := ./cmd/cashier
-CASHIER_BIN := ./cashier
-CASHIERD_BIN := ./cashierd
 CASHIERD_CMD := ./cmd/cashierd
 SRC_FILES = $(shell find * -type f -name '*.go' -not -path 'vendor/*' -not -name 'a_*-packr.go')
+VERSION_PKG := "github.com/nsheridan/cashier/lib.Version"
+VERSION := $(shell git describe --tags --always --dirty)
+
+GOOS ?= $(shell go env GOOS)
+GOARCH ?= $(shell go env GOARCH)
+CGO_ENABLED ?= $(shell go env CGO_ENABLED)
 
 all: test build
 
@@ -20,11 +24,11 @@ build: cashier cashierd
 generate:
 	go generate -x ./...
 
-cashier:
-	go build -o cashier $(CASHIER_CMD)
+%-cmd:
+	CGO_ENABLED=$(CGO_ENABLED) GOARCH=$(GOARCH) GOOS=$(GOOS) go build -ldflags="-X $(VERSION_PKG)=$(VERSION)" -o $* ./cmd/$*
 
-cashierd: generate
-	go build -o cashierd $(CASHIERD_CMD)
+install-%: generate
+	CGO_ENABLED=$(CGO_ENABLED) GOARCH=$(GOARCH) GOOS=$(GOOS) go install -x -ldflags="-X $(VERSION_PKG)=$(VERSION)" ./cmd/$*
 
 clean:
 	rm -f cashier cashierd
@@ -37,4 +41,7 @@ dep:
 	go get -u golang.org/x/lint/golint
 	go get -u golang.org/x/tools/cmd/goimports
 
+version:
+	@echo $(VERSION)
+
 .PHONY: all build dep generate test cashier cashierd clean migration
diff --git a/client/client.go b/client/client.go
index 628783ab537771326679d5fbb77239ffe4a8ec30..dc4ada147b7f3ec9e4684bfbf01a32d587b8ee0f 100644
--- a/client/client.go
+++ b/client/client.go
@@ -142,6 +142,7 @@ func Sign(pub ssh.PublicKey, token string, conf *Config) (*ssh.Certificate, erro
 	s := &lib.SignRequest{
 		Key:        string(lib.GetPublicKey(pub)),
 		ValidUntil: time.Now().Add(validity),
+		Version:    lib.Version,
 	}
 	resp := &lib.SignResponse{}
 	for {
diff --git a/cmd/cashier/main.go b/cmd/cashier/main.go
index 1ee945592643429a2e464719ad2d6f0b1e3c6283..54fad82d9563f3fd1cc36d06efbde0fdaede25a5 100644
--- a/cmd/cashier/main.go
+++ b/cmd/cashier/main.go
@@ -13,23 +13,29 @@ import (
 	"time"
 
 	"github.com/nsheridan/cashier/client"
+	"github.com/nsheridan/cashier/lib"
 	"github.com/pkg/browser"
 	"github.com/spf13/pflag"
 	"golang.org/x/crypto/ssh/agent"
 )
 
 var (
-	u, _             = user.Current()
-	cfg              = pflag.String("config", path.Join(u.HomeDir, ".cashier.conf"), "Path to config file")
-	ca               = pflag.String("ca", "http://localhost:10000", "CA server")
-	keysize          = pflag.Int("key_size", 0, "Size of key to generate. Ignored for ed25519 keys. (default 2048 for rsa keys, 256 for ecdsa keys)")
-	validity         = pflag.Duration("validity", time.Hour*24, "Key lifetime. May be overridden by the CA at signing time")
-	keytype          = pflag.String("key_type", "", "Type of private key to generate - rsa, ecdsa or ed25519. (default \"rsa\")")
-	publicFilePrefix = pflag.String("key_file_prefix", "", "Prefix for filename for public key and cert (optional, no default)")
+	u, _    = user.Current()
+	cfg     = pflag.String("config", path.Join(u.HomeDir, ".cashier.conf"), "Path to config file")
+	_       = pflag.String("ca", "http://localhost:10000", "CA server")
+	_       = pflag.Int("key_size", 0, "Size of key to generate. Ignored for ed25519 keys. (default 2048 for rsa keys, 256 for ecdsa keys)")
+	_       = pflag.Duration("validity", time.Hour*24, "Key lifetime. May be overridden by the CA at signing time")
+	_       = pflag.String("key_type", "", "Type of private key to generate - rsa, ecdsa or ed25519. (default \"rsa\")")
+	_       = pflag.String("key_file_prefix", "", "Prefix for filename for public key and cert (optional, no default)")
+	version = pflag.Bool("version", false, "Print version and exit")
 )
 
 func main() {
 	pflag.Parse()
+	if *version {
+		fmt.Printf("%s\n", lib.Version)
+		os.Exit(0)
+	}
 	log.SetPrefix("cashier: ")
 	log.SetFlags(0)
 	var err error
diff --git a/cmd/cashierd/main.go b/cmd/cashierd/main.go
index 2e378bcb42adc08a729196db09fe1829f6281ac6..5b0b390a34a09e8fab426a33910bac09241bf714 100644
--- a/cmd/cashierd/main.go
+++ b/cmd/cashierd/main.go
@@ -2,8 +2,11 @@ package main
 
 import (
 	"flag"
+	"fmt"
 	"log"
+	"os"
 
+	"github.com/nsheridan/cashier/lib"
 	"github.com/nsheridan/cashier/server"
 	"github.com/nsheridan/cashier/server/config"
 	"github.com/nsheridan/cashier/server/wkfs/vaultfs"
@@ -11,11 +14,16 @@ import (
 )
 
 var (
-	cfg = flag.String("config_file", "cashierd.conf", "Path to configuration file.")
+	cfg     = flag.String("config_file", "cashierd.conf", "Path to configuration file.")
+	version = flag.Bool("version", false, "Print version and exit")
 )
 
 func main() {
 	flag.Parse()
+	if *version {
+		fmt.Printf("%s\n", lib.Version)
+		os.Exit(0)
+	}
 	conf, err := config.ReadConfig(*cfg)
 	if err != nil {
 		log.Fatal(err)
diff --git a/lib/proto.go b/lib/proto.go
index a67ad47162ac28e72397467e999b45c177234f58..5d8c67ac5f59bd6bfa0e390911bd364b036264d3 100644
--- a/lib/proto.go
+++ b/lib/proto.go
@@ -7,10 +7,12 @@ type SignRequest struct {
 	Key        string    `json:"key"`
 	ValidUntil time.Time `json:"valid_until"`
 	Message    string    `json:"message"`
+	Version    string    `json:"version"`
 }
 
 // SignResponse is sent by the server.
 type SignResponse struct {
 	Status   string `json:"status"`   // Status will be "ok" or "error".
 	Response string `json:"response"` // Response will contain either the signed certificate or the error message.
+	Version  string `json:"version"`
 }
diff --git a/lib/version.go b/lib/version.go
new file mode 100644
index 0000000000000000000000000000000000000000..b59bca4f7a381b095e5e9adfdf0872a670104763
--- /dev/null
+++ b/lib/version.go
@@ -0,0 +1,4 @@
+package lib
+
+// Version string
+var Version = "unknown"
diff --git a/server/web.go b/server/web.go
index d55aa523a34aa02910110734e430fcc158de57fb..840ce1ba1c9113c5c338caa89e61fe85b2cce681 100644
--- a/server/web.go
+++ b/server/web.go
@@ -306,6 +306,14 @@ func newState() string {
 	return hex.EncodeToString(k)
 }
 
+// mwVersion is middleware to add a X-Cashier-Version header to the response.
+func mwVersion(next http.Handler) http.Handler {
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		w.Header().Set("X-Cashier-Version", lib.Version)
+		next.ServeHTTP(w, r)
+	})
+}
+
 func runHTTPServer(conf *config.Server, l net.Listener) {
 	var err error
 	ctx := &appContext{
@@ -330,6 +338,7 @@ func runHTTPServer(conf *config.Server, l net.Listener) {
 
 	CSRF := csrf.Protect([]byte(conf.CSRFSecret), csrf.Secure(conf.UseTLS))
 	r := mux.NewRouter()
+	r.Use(mwVersion)
 	r.Methods("GET").Path("/").Handler(appHandler{ctx, rootHandler})
 	r.Methods("GET").Path("/auth/login").Handler(appHandler{ctx, loginHandler})
 	r.Methods("GET").Path("/auth/callback").Handler(appHandler{ctx, callbackHandler})