diff --git a/caddy/caddy.go b/caddy/caddy.go
index 952480769..ae01593ec 100644
--- a/caddy/caddy.go
+++ b/caddy/caddy.go
@@ -23,7 +23,6 @@ import (
 	"errors"
 	"fmt"
 	"io/ioutil"
-	"log"
 	"net"
 	"os"
 	"path"
@@ -183,25 +182,25 @@ func startServers(groupings Group) error {
 	for _, group := range groupings {
 		s, err := server.New(group.BindAddr.String(), group.Configs)
 		if err != nil {
-			log.Fatal(err)
+			return err
 		}
 		s.HTTP2 = HTTP2 // TODO: This setting is temporary
 
 		var ln server.ListenerFile
 		if IsRestart() {
 			// Look up this server's listener in the map of inherited file descriptors;
-			// if we don't have one, we must make a new one.
+			// if we don't have one, we must make a new one (later).
 			if fdIndex, ok := loadedGob.ListenerFds[s.Addr]; ok {
 				file := os.NewFile(fdIndex, "")
 
 				fln, err := net.FileListener(file)
 				if err != nil {
-					log.Fatal(err)
+					return err
 				}
 
 				ln, ok = fln.(server.ListenerFile)
 				if !ok {
-					log.Fatal("listener was not a ListenerFile")
+					return errors.New("listener for " + s.Addr + " was not a ListenerFile")
 				}
 
 				delete(loadedGob.ListenerFds, s.Addr) // mark it as used
diff --git a/main.go b/main.go
index 3aa99a4dd..e2e6b552d 100644
--- a/main.go
+++ b/main.go
@@ -20,6 +20,7 @@ var (
 	cpu     string
 	version bool
 	revoke  string
+	logfile string
 )
 
 const (
@@ -43,14 +44,31 @@ func init() {
 	flag.BoolVar(&letsencrypt.Agreed, "agree", false, "Agree to Let's Encrypt Subscriber Agreement")
 	flag.StringVar(&letsencrypt.DefaultEmail, "email", "", "Default Let's Encrypt account email address")
 	flag.StringVar(&revoke, "revoke", "", "Hostname for which to revoke the certificate")
+	flag.StringVar(&logfile, "log", "", "Process log file")
 }
 
 func main() {
-	flag.Parse()
+	flag.Parse() // called here in main() to allow other packages to set flags in their inits
 
 	caddy.AppName = appName
 	caddy.AppVersion = appVersion
 
+	// set up process log before anything bad happens
+	switch logfile {
+	case "stdout":
+		log.SetOutput(os.Stdout)
+	case "stderr":
+		log.SetOutput(os.Stderr)
+	case "":
+		log.SetOutput(ioutil.Discard)
+	default:
+		file, err := os.Create(logfile)
+		if err != nil {
+			log.Fatalf("Error opening log file: %v", err)
+		}
+		log.SetOutput(file)
+	}
+
 	if version {
 		fmt.Printf("%s %s\n", caddy.AppName, caddy.AppVersion)
 		os.Exit(0)
@@ -67,13 +85,13 @@ func main() {
 	// Set CPU cap
 	err := setCPU(cpu)
 	if err != nil {
-		log.Fatal(err)
+		mustLogFatal(err)
 	}
 
 	// Get Caddyfile input
 	caddyfile, err := caddy.LoadCaddyfile(loadCaddyfile)
 	if err != nil {
-		log.Fatal(err)
+		mustLogFatal(err)
 	}
 
 	// Start your engines
@@ -82,7 +100,7 @@ func main() {
 		if caddy.IsRestart() {
 			log.Println("error starting servers:", err)
 		} else {
-			log.Fatal(err)
+			mustLogFatal(err)
 		}
 	}
 
@@ -90,6 +108,14 @@ func main() {
 	caddy.Wait()
 }
 
+// mustLogFatal just wraps log.Fatal() in a way that ensures the
+// output is always printed to stderr so the user can see it,
+// even if the process log was not enabled.
+func mustLogFatal(args ...interface{}) {
+	log.SetOutput(os.Stderr)
+	log.Fatal(args...)
+}
+
 func loadCaddyfile() (caddy.Input, error) {
 	// -conf flag
 	if conf != "" {