diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json
index b173eda0a5268da9219a7be38ee489b2e4fd8fa4..4998c189d55e9afb0f5378a76517569d71d71fac 100644
--- a/Godeps/Godeps.json
+++ b/Godeps/Godeps.json
@@ -8,6 +8,10 @@
 			"Comment": "2016.08.31",
 			"Rev": "ec89d50f00d39494f5b3ec5cf2fe75c53467a937"
 		},
+		{
+			"ImportPath": "github.com/client9/reopen",
+			"Rev": "4b86f9c0ead51cc410d05655596e30f281ed9071"
+		},
 		{
 			"ImportPath": "github.com/dgrijalva/jwt-go",
 			"Comment": "v3.0.0",
diff --git a/internal/helper/logging.go b/internal/helper/logging.go
index d5d51d982ca6fa4a74aaa786cb72790e12c94a3e..0a51d1849a322cb1b360a8da0efb7ad5f36bd6c2 100644
--- a/internal/helper/logging.go
+++ b/internal/helper/logging.go
@@ -2,10 +2,23 @@ package helper
 
 import (
 	"fmt"
+	"io"
+	"log"
 	"net/http"
+	"os"
 	"time"
 )
 
+var responseLogger *log.Logger
+
+func init() {
+	SetCustomResponseLogger(os.Stderr)
+}
+
+func SetCustomResponseLogger(writer io.Writer) {
+	responseLogger = log.New(writer, "", 0)
+}
+
 type LoggingResponseWriter struct {
 	rw      http.ResponseWriter
 	status  int
@@ -44,7 +57,7 @@ func (l *LoggingResponseWriter) WriteHeader(status int) {
 
 func (l *LoggingResponseWriter) Log(r *http.Request) {
 	duration := time.Since(l.started)
-	fmt.Printf("%s %s - - [%s] %q %d %d %q %q %f\n",
+	responseLogger.Printf("%s %s - - [%s] %q %d %d %q %q %f\n",
 		r.Host, r.RemoteAddr, l.started,
 		fmt.Sprintf("%s %s %s", r.Method, r.RequestURI, r.Proto),
 		l.status, l.written, r.Referer(), r.UserAgent(), duration.Seconds(),
diff --git a/logging.go b/logging.go
new file mode 100644
index 0000000000000000000000000000000000000000..cad98dcd8be42ca53887d23547665d0ab85bf672
--- /dev/null
+++ b/logging.go
@@ -0,0 +1,39 @@
+package main
+
+import (
+	"log"
+	"os"
+	"os/signal"
+	"syscall"
+
+	"gitlab.com/gitlab-org/gitlab-workhorse/internal/helper"
+
+	"github.com/client9/reopen"
+)
+
+func reopenLogWriter(l reopen.WriteCloser, sighup chan os.Signal) {
+	for _ = range sighup {
+		log.Printf("Reopening log file")
+		l.Reopen()
+	}
+}
+
+func startLogging(logFile string) {
+	var logWriter = reopen.Stderr
+
+	if logFile != "" {
+		file, err := reopen.NewFileWriter(logFile)
+		if err != nil {
+			log.Fatalf("Unable to set output log: %s", err)
+		}
+		logWriter = file
+	}
+
+	log.SetOutput(logWriter)
+	helper.SetCustomResponseLogger(logWriter)
+
+	sighup := make(chan os.Signal, 1)
+	signal.Notify(sighup, syscall.SIGHUP)
+
+	go reopenLogWriter(logWriter, sighup)
+}
diff --git a/main.go b/main.go
index 54ece4b5daefb06a43860bf94ec3d84fc5df6747..daff549fa87e7f5c1b8a12ddc05f02367ece3a3d 100644
--- a/main.go
+++ b/main.go
@@ -45,6 +45,7 @@ var secretPath = flag.String("secretPath", "./.gitlab_workhorse_secret", "File w
 var apiLimit = flag.Uint("apiLimit", 0, "Number of API requests allowed at single time")
 var apiQueueLimit = flag.Uint("apiQueueLimit", 0, "Number of API requests allowed to be queued")
 var apiQueueTimeout = flag.Duration("apiQueueDuration", queueing.DefaultTimeout, "Maximum queueing duration of requests")
+var logFile = flag.String("logFile", "", "Log file to be used")
 
 func main() {
 	flag.Usage = func() {
@@ -60,10 +61,11 @@ func main() {
 		os.Exit(0)
 	}
 
+	startLogging(*logFile)
+
 	backendURL, err := parseAuthBackend(*authBackend)
 	if err != nil {
-		fmt.Fprintf(os.Stderr, "invalid authBackend: %v\n", err)
-		os.Exit(1)
+		log.Fatalf("invalid authBackend: %v", err)
 	}
 
 	log.Printf("Starting %s", version)
diff --git a/vendor/github.com/client9/reopen/.gitignore b/vendor/github.com/client9/reopen/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..41aacb3ae785df5eec38428cad9ad4c803d9680e
--- /dev/null
+++ b/vendor/github.com/client9/reopen/.gitignore
@@ -0,0 +1,27 @@
+# emacs turds
+*~
+
+# Compiled Object files, Static and Dynamic libs (Shared Objects)
+*.o
+*.a
+*.so
+
+# Folders
+_obj
+_test
+
+# Architecture specific extensions/prefixes
+*.[568vq]
+[568vq].out
+
+*.cgo1.go
+*.cgo2.c
+_cgo_defun.c
+_cgo_gotypes.go
+_cgo_export.*
+
+_testmain.go
+
+*.exe
+*.test
+*.prof
diff --git a/vendor/github.com/client9/reopen/.travis.yml b/vendor/github.com/client9/reopen/.travis.yml
new file mode 100644
index 0000000000000000000000000000000000000000..98f9246dfc74c8190f67c935b9d374d43f66d095
--- /dev/null
+++ b/vendor/github.com/client9/reopen/.travis.yml
@@ -0,0 +1,5 @@
+sudo: required
+dist: trusty
+language: generic
+script:
+  - make -e docker-ci
diff --git a/vendor/github.com/client9/reopen/LICENSE b/vendor/github.com/client9/reopen/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..4345e0bafef120715bb9a30dc68ffc7f627d594b
--- /dev/null
+++ b/vendor/github.com/client9/reopen/LICENSE
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Nick Galbreath
+
+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/client9/reopen/Makefile b/vendor/github.com/client9/reopen/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..35283b5bdc086d2329855554d75c681075d18319
--- /dev/null
+++ b/vendor/github.com/client9/reopen/Makefile
@@ -0,0 +1,31 @@
+all: build test lint
+
+build:
+	go build ./...
+
+test:
+	go test ./...
+
+lint:
+	golint ./...
+	gofmt -w -s . ./example*
+	goimports -w . ./example*
+
+clean:
+	rm -f *~ ./example*/*~
+	rm -f ./example1/example1
+	rm -f ./example2/example2
+	go clean ./...
+	git gc
+
+ci: build test lint
+
+docker-ci:
+	docker run --rm \
+		-e COVERALLS_REPO_TOKEN=$(COVERALLS_REPO_TOKEN) \
+		-v $(PWD):/go/src/github.com/client9/reopen \
+		-w /go/src/github.com/client9/reopen \
+		nickg/golang-dev-docker \
+		make ci
+
+.PHONY: ci docker-ci
diff --git a/vendor/github.com/client9/reopen/README.md b/vendor/github.com/client9/reopen/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..4c9cf2366287c3b23494c2eca0c4adf371fd64b7
--- /dev/null
+++ b/vendor/github.com/client9/reopen/README.md
@@ -0,0 +1,74 @@
+[![Build Status](https://travis-ci.org/client9/reopen.svg)](https://travis-ci.org/client9/reopen) [![Go Report Card](http://goreportcard.com/badge/client9/reopen)](http://goreportcard.com/report/client9/reopen) [![GoDoc](https://godoc.org/github.com/client9/reopen?status.svg)](https://godoc.org/github.com/client9/reopen) [![Coverage](http://gocover.io/_badge/github.com/client9/reopen)](http://gocover.io/github.com/client9/reopen) [![license](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](https://raw.githubusercontent.com/client9/reopen/master/LICENSE)
+
+Makes a standard os.File a "reopenable writer" and allows SIGHUP signals
+to reopen log files, as needed by
+[logrotated](https://fedorahosted.org/logrotate/).  This is inspired
+by the C/Posix
+[freopen](http://pubs.opengroup.org/onlinepubs/009695399/functions/freopen.html)
+
+The simple version `reopen.NewFileWriter` does unbuffered writing.  A
+call to `.Reopen` closes the existing file handle, and then re-opens
+it using the original filename.
+
+The more advanced version `reopen.NewBufferedFileWriter` buffers input
+and flushes when the internal buffer is full (with care) or if 30 seconds has
+elapsed.
+
+There is also `reopen.Stderr` and `reopen.Stdout` which implements the `reopen.Reopener` interface (and does nothing on a reopen call).
+
+`reopen.Discard` wraps `ioutil.Discard`
+
+Samples are in `example1` and `example2`.  The `run.sh` scripts are a
+dumb test where the file is rotated underneath the server, and nothing
+is lost.  This is not the most robust test but gives you an idea of how it works.
+
+
+Here's some sample code.
+
+```go
+package main
+
+/* Simple logrotate logger
+ */
+import (
+       "fmt"
+       "log"
+       "net/http"
+       "os"
+       "os/signal"
+       "syscall"
+
+       "github.com/client9/reopen"
+)
+
+func main() {
+        // setup logger to write to our new *reopenable* log file
+
+        f, err := reopen.NewFileWriter("/tmp/example.log")
+        if err != nil {
+           log.Fatalf("Unable to set output log: %s", err)
+        }
+        log.SetOutput(f)
+
+        // Handle SIGHUP
+        //
+        // channel is number of signals needed to catch  (more or less)
+        // we only are working with one here, SIGHUP
+        sighup := make(chan os.Signal, 1)
+        signal.Notify(sighup, syscall.SIGHUP)
+        go func() {
+           for {
+               <-sighup
+              fmt.Println("Got a sighup")
+              f.Reopen()
+            }
+         }()
+
+        // dumb http server that just prints and logs the path
+        http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+            log.Printf("%s", r.URL.Path)
+            fmt.Fprintf(w, "%s\n", r.URL.Path)
+        })
+        log.Fatal(http.ListenAndServe("127.0.0.1:8123", nil))
+}
+```
diff --git a/vendor/github.com/client9/reopen/reopen.go b/vendor/github.com/client9/reopen/reopen.go
new file mode 100644
index 0000000000000000000000000000000000000000..08cc0cead08af607c48019a72203952341945959
--- /dev/null
+++ b/vendor/github.com/client9/reopen/reopen.go
@@ -0,0 +1,232 @@
+package reopen
+
+import (
+	"bufio"
+	"io"
+	"io/ioutil"
+	"os"
+	"sync"
+	"time"
+)
+
+// Reopener interface defines something that can be reopened
+type Reopener interface {
+	Reopen() error
+}
+
+// Writer is a writer that also can be reopened
+type Writer interface {
+	Reopener
+	io.Writer
+}
+
+// WriteCloser is a io.WriteCloser that can also be reopened
+type WriteCloser interface {
+	Reopener
+	io.WriteCloser
+}
+
+// FileWriter that can also be reopened
+type FileWriter struct {
+	mu   sync.Mutex // ensures close / reopen / write are not called at the same time, protects f
+	f    *os.File
+	mode os.FileMode
+	name string
+}
+
+// Close calls the underlyding File.Close()
+func (f *FileWriter) Close() error {
+	f.mu.Lock()
+	err := f.f.Close()
+	f.mu.Unlock()
+	return err
+}
+
+// mutex free version
+func (f *FileWriter) reopen() error {
+	if f.f != nil {
+		f.f.Close()
+		f.f = nil
+	}
+	newf, err := os.OpenFile(f.name, os.O_WRONLY|os.O_APPEND|os.O_CREATE, f.mode)
+	if err != nil {
+		f.f = nil
+		return err
+	}
+	f.f = newf
+
+	return nil
+}
+
+// Reopen the file
+func (f *FileWriter) Reopen() error {
+	f.mu.Lock()
+	err := f.reopen()
+	f.mu.Unlock()
+	return err
+}
+
+// Write implements the stander io.Writer interface
+func (f *FileWriter) Write(p []byte) (int, error) {
+	f.mu.Lock()
+	n, err := f.f.Write(p)
+	f.mu.Unlock()
+	return n, err
+}
+
+// NewFileWriter opens a file for appending and writing and can be reopened.
+// it is a ReopenWriteCloser...
+func NewFileWriter(name string) (*FileWriter, error) {
+	// Standard default mode
+	return NewFileWriterMode(name, 0666)
+}
+
+// NewFileWriterMode opens a Reopener file with a specific permission
+func NewFileWriterMode(name string, mode os.FileMode) (*FileWriter, error) {
+	writer := FileWriter{
+		f:    nil,
+		name: name,
+		mode: mode,
+	}
+	err := writer.reopen()
+	if err != nil {
+		return nil, err
+	}
+	return &writer, nil
+}
+
+// BufferedFileWriter is buffer writer than can be reopned
+type BufferedFileWriter struct {
+	mu         sync.Mutex
+	OrigWriter *FileWriter
+	BufWriter  *bufio.Writer
+}
+
+// Reopen implement Reopener
+func (bw *BufferedFileWriter) Reopen() error {
+	bw.mu.Lock()
+	bw.BufWriter.Flush()
+
+	// use non-mutex version since we are using this one
+	err := bw.OrigWriter.reopen()
+
+	bw.BufWriter.Reset(io.Writer(bw.OrigWriter))
+	bw.mu.Unlock()
+
+	return err
+}
+
+// Close flushes the internal buffer and closes the destination file
+func (bw *BufferedFileWriter) Close() error {
+	bw.mu.Lock()
+	bw.BufWriter.Flush()
+	bw.OrigWriter.f.Close()
+	bw.mu.Unlock()
+	return nil
+}
+
+// Write implements io.Writer (and reopen.Writer)
+func (bw *BufferedFileWriter) Write(p []byte) (int, error) {
+	bw.mu.Lock()
+	n, err := bw.BufWriter.Write(p)
+
+	// Special Case... if the used space in the buffer is LESS than
+	// the input, then we did a flush in the middle of the line
+	// and the full log line was not sent on its way.
+	if bw.BufWriter.Buffered() < len(p) {
+		bw.BufWriter.Flush()
+	}
+
+	bw.mu.Unlock()
+	return n, err
+}
+
+// flushDaemon periodically flushes the log file buffers.
+//  props to glog
+func (bw *BufferedFileWriter) flushDaemon() {
+	for range time.NewTicker(flushInterval).C {
+		bw.mu.Lock()
+		bw.BufWriter.Flush()
+		bw.OrigWriter.f.Sync()
+		bw.mu.Unlock()
+	}
+}
+
+const bufferSize = 256 * 1024
+const flushInterval = 30 * time.Second
+
+// NewBufferedFileWriter opens a buffered file that is periodically
+//  flushed.
+// TODO: allow size and interval to be passed in.
+func NewBufferedFileWriter(w *FileWriter) *BufferedFileWriter {
+	bw := BufferedFileWriter{
+		OrigWriter: w,
+		BufWriter:  bufio.NewWriterSize(w, bufferSize),
+	}
+	go bw.flushDaemon()
+	return &bw
+}
+
+type multiReopenWriter struct {
+	writers []Writer
+}
+
+// Reopen reopens all child Reopeners
+func (t *multiReopenWriter) Reopen() error {
+	for _, w := range t.writers {
+		err := w.Reopen()
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// Write implements standard io.Write and reopen.Write
+func (t *multiReopenWriter) Write(p []byte) (int, error) {
+	for _, w := range t.writers {
+		n, err := w.Write(p)
+		if err != nil {
+			return n, err
+		}
+		if n != len(p) {
+			return n, io.ErrShortWrite
+		}
+	}
+	return len(p), nil
+}
+
+// MultiWriter creates a writer that duplicates its writes to all the
+// provided writers, similar to the Unix tee(1) command.
+//  Also allow reopen
+func MultiWriter(writers ...Writer) Writer {
+	w := make([]Writer, len(writers))
+	copy(w, writers)
+	return &multiReopenWriter{w}
+}
+
+type nopReopenWriteCloser struct {
+	io.Writer
+}
+
+func (nopReopenWriteCloser) Reopen() error {
+	return nil
+}
+
+func (nopReopenWriteCloser) Close() error {
+	return nil
+}
+
+// NopWriter turns a normal writer into a ReopenWriter
+//  by doing a NOP on Reopen
+// TODO: better name
+func NopWriter(w io.Writer) WriteCloser {
+	return nopReopenWriteCloser{w}
+}
+
+// Reopenable versions of os.Stdout, os.Stderr, /dev/null (reopen does nothing)
+var (
+	Stdout  = NopWriter(os.Stdout)
+	Stderr  = NopWriter(os.Stderr)
+	Discard = NopWriter(ioutil.Discard)
+)