diff --git a/.gitignore b/.gitignore
index a89eb3c6d205512b95a46a9887b65bcb00feae24..83058e7e22da20d75231a6db31d05339922aac35 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
 # Created by .ignore support plugin (hsz.mobi)
 shared/pages/.update
+/gitlab-pages
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 205463ff0985dd50de7554cea190da9ca30e4891..294339011bb033db4b48736eeaa2c6cfdfca8c94 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -11,3 +11,4 @@ before_script:
 test:
   script:
   - make verify
+  - make acceptance
diff --git a/CHANGELOG b/CHANGELOG
index f8e8304628d643d77111c732b27c7b3d4e63f713..c5becd1df2e08d289e40d7dee593a4bc2abd4432 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,6 @@
+v 0.2.5
+- Allow listen-http, listen-https and listen-proxy to be specified multiple times
+
 v 0.2.4
 - Fix predefined 404 page content-type
 
diff --git a/Makefile b/Makefile
index 3ad0447d877438b68cc4a945c4042aa5bfe4db30..3fdf74968f561b73e6d31597410dd55ba3e5d429 100644
--- a/Makefile
+++ b/Makefile
@@ -41,5 +41,8 @@ test:
 	go get golang.org/x/tools/cmd/cover
 	go test ./... -cover
 
+acceptance: gitlab-pages
+	go test ./... -run-acceptance-tests
+
 docker:
 	docker run --rm -it -v ${PWD}:/go/src/pages -w /go/src/pages golang:1.5 /bin/bash
diff --git a/README.md b/README.md
index cf508cfd5fe5025ad01b57e6c57f4af1fd37dc5c..087a21525715f922822d8141c7e1223d05269c44 100644
--- a/README.md
+++ b/README.md
@@ -60,6 +60,18 @@ go build
 sudo ./gitlab-pages -listen-http ":80" -pages-root path/to/gitlab/shared/pages -pages-domain example.com -daemon-uid 1000 -daemon-gid 1000
 ```
 
+### Listen on multiple ports
+
+Each of the `listen-http`, `listen-https` and `listen-proxy` arguments can be provided multiple times. Gitlab Pages will accept connections to them all.
+
+Example:
+```
+go build
+./gitlab-pages -listen-http "10.0.0.1:8080" -listen-https "[fd00::1]:8080" -pages-root path/to/gitlab/shared/pages -pages-domain example.com
+```
+
+This is most useful in dual-stack environments (IPv4+IPv6) where both Gitlab Pages and another HTTP server have to co-exist on the same server.
+
 ### License
 
 MIT
diff --git a/VERSION b/VERSION
index abd410582dea1b6dcb53bcfd93921df71212b778..3a4036fb450f1771defbe73fe2904f3f841adcb5 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.2.4
+0.2.5
diff --git a/acceptance_test.go b/acceptance_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..2e36acd12db29cf3ac5d0d5d139cbd17c732ae0c
--- /dev/null
+++ b/acceptance_test.go
@@ -0,0 +1,62 @@
+package main
+
+import (
+	"flag"
+	"github.com/stretchr/testify/assert"
+	"net/http"
+	"testing"
+)
+
+var shouldRun = flag.Bool("run-acceptance-tests", false, "Run the acceptance tests?")
+var pagesBinary = flag.String("gitlab-pages-binary", "./gitlab-pages", "Path to the gitlab-pages binary")
+
+// TODO: Use TCP port 0 everywhere to avoid conflicts. The binary could output
+// the actual port (and type of listener) for us to read in place of the
+// hardcoded values below.
+var listeners = []ListenSpec{
+	{"http", "127.0.0.1", "37000"},
+	{"http", "::1", "37000"},
+	{"https", "127.0.0.1", "37001"},
+	{"https", "::1", "37001"},
+	{"proxy", "127.0.0.1", "37002"},
+	{"proxy", "::1", "37002"},
+}
+
+func skipUnlessEnabled(t *testing.T) {
+	if *shouldRun {
+		return
+	}
+
+	t.Log("Acceptance tests disabled")
+	t.SkipNow()
+}
+
+func TestUnknownHostReturnsNotFound(t *testing.T) {
+	skipUnlessEnabled(t)
+	teardown := RunPagesProcess(t, *pagesBinary, listeners)
+	defer teardown()
+
+	for _, spec := range listeners {
+		rsp, err := GetPageFromListener(t, spec, "invalid.invalid", "")
+
+		if assert.NoError(t, err) {
+			rsp.Body.Close()
+			assert.Equal(t, http.StatusNotFound, rsp.StatusCode)
+		}
+	}
+}
+
+func TestKnownHostReturns200(t *testing.T) {
+	skipUnlessEnabled(t)
+	teardown := RunPagesProcess(t, *pagesBinary, listeners)
+	defer teardown()
+
+	for _, spec := range listeners {
+		rsp, err := GetPageFromListener(t, spec, "group.gitlab-example.com", "project/")
+
+		if assert.NoError(t, err) {
+			rsp.Body.Close()
+			assert.Equal(t, http.StatusOK, rsp.StatusCode)
+		}
+	}
+}
diff --git a/app.go b/app.go
index aa0438ff91a0898f8f56fe18f58f6a512fee579c..35b661cca64451abaa44dd110e40c82d8763f23a 100644
--- a/app.go
+++ b/app.go
@@ -85,39 +85,40 @@ func (a *theApp) UpdateDomains(domains domains) {
 func (a *theApp) Run() {
 	var wg sync.WaitGroup
 
-	if a.ListenHTTP != 0 {
+	// Listen for HTTP
+	for _, fd := range a.ListenHTTP {
 		wg.Add(1)
-		go func() {
+		go func(fd uintptr) {
 			defer wg.Done()
-			err := listenAndServe(a.ListenHTTP, a.ServeHTTP, a.HTTP2, nil)
+			err := listenAndServe(fd, a.ServeHTTP, a.HTTP2, nil)
 			if err != nil {
 				log.Fatal(err)
 			}
-		}()
+		}(fd)
 	}
 
 	// Listen for HTTPS
-	if a.ListenHTTPS != 0 {
+	for _, fd := range a.ListenHTTPS {
 		wg.Add(1)
-		go func() {
+		go func(fd uintptr) {
 			defer wg.Done()
-			err := listenAndServeTLS(a.ListenHTTPS, a.RootCertificate, a.RootKey, a.ServeHTTP, a.ServeTLS, a.HTTP2)
+			err := listenAndServeTLS(fd, a.RootCertificate, a.RootKey, a.ServeHTTP, a.ServeTLS, a.HTTP2)
 			if err != nil {
 				log.Fatal(err)
 			}
-		}()
+		}(fd)
 	}
 
 	// Listen for HTTP proxy requests
-	if a.ListenProxy != 0 {
+	for _, fd := range a.ListenProxy {
 		wg.Add(1)
-		go func() {
+		go func(fd uintptr) {
 			defer wg.Done()
-			err := listenAndServe(a.ListenProxy, a.ServeProxy, a.HTTP2, nil)
+			err := listenAndServe(fd, a.ServeProxy, a.HTTP2, nil)
 			if err != nil {
 				log.Fatal(err)
 			}
-		}()
+		}(fd)
 	}
 
 	go watchDomains(a.Domain, a.UpdateDomains, time.Second)
diff --git a/app_config.go b/app_config.go
index 8da2437a6a62a492fc9afdff379b699fdca8f5ae..7ffa1e764328ad2bb3911f69f30149558073e40d 100644
--- a/app_config.go
+++ b/app_config.go
@@ -6,9 +6,9 @@ type appConfig struct {
 	RootCertificate []byte
 	RootKey         []byte
 
-	ListenHTTP  uintptr
-	ListenHTTPS uintptr
-	ListenProxy uintptr
+	ListenHTTP  []uintptr
+	ListenHTTPS []uintptr
+	ListenProxy []uintptr
 
 	HTTP2        bool
 	RedirectHTTP bool
diff --git a/daemon.go b/daemon.go
index de6ce4a3426c22b9257c5f9b3ade2c6b67765ab3..69feed1c1aa9f26e7730977c4cf3271885cfeab9 100644
--- a/daemon.go
+++ b/daemon.go
@@ -56,15 +56,20 @@ func daemonReexec(uid, gid uint, args ...string) (cmd *exec.Cmd, err error) {
 	return
 }
 
-func daemonUpdateFd(cmd *exec.Cmd, fd *uintptr) {
-	if *fd == 0 {
-		return
-	}
+func daemonUpdateFd(cmd *exec.Cmd, fd uintptr) (childFd uintptr) {
+	file := os.NewFile(fd, "[socket]")
 
-	file := os.NewFile(*fd, "[socket]")
 	// we add 3 since, we have a 3 predefined FDs
-	*fd = uintptr(3 + len(cmd.ExtraFiles))
+	childFd = uintptr(3 + len(cmd.ExtraFiles))
 	cmd.ExtraFiles = append(cmd.ExtraFiles, file)
+
+	return
+}
+
+func daemonUpdateFds(cmd *exec.Cmd, fds []uintptr) {
+	for idx, fd := range fds {
+		fds[idx] = daemonUpdateFd(cmd, fd)
+	}
 }
 
 func killProcess(cmd *exec.Cmd) {
@@ -196,10 +201,10 @@ func daemonize(config appConfig, uid, gid uint) {
 	defer configWriter.Close()
 	cmd.ExtraFiles = append(cmd.ExtraFiles, configReader)
 
-	// Create a new file and store the FD
-	daemonUpdateFd(cmd, &config.ListenHTTP)
-	daemonUpdateFd(cmd, &config.ListenHTTPS)
-	daemonUpdateFd(cmd, &config.ListenProxy)
+	// Create a new file and store the FD for each listener
+	daemonUpdateFds(cmd, config.ListenHTTP)
+	daemonUpdateFds(cmd, config.ListenHTTPS)
+	daemonUpdateFds(cmd, config.ListenProxy)
 
 	// Start the process
 	if err = cmd.Start(); err != nil {
diff --git a/domain_test.go b/domain_test.go
index 9032aeeedf3a33112c0124866fc32386012b91ff..7298c22b83801ebc38674c375825f9345d4549d3 100644
--- a/domain_test.go
+++ b/domain_test.go
@@ -139,38 +139,9 @@ func TestDomainCertificate(t *testing.T) {
 		Group:   "group",
 		Project: "project2",
 		Config: &domainConfig{
-			Domain: "test.domain.com",
-			Certificate: `-----BEGIN CERTIFICATE-----
-MIICWDCCAcGgAwIBAgIJAMyzCfoGEwVNMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
-BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
-aWRnaXRzIFB0eSBMdGQwHhcNMTYwMjExMTcxNzM2WhcNMjYwMjA4MTcxNzM2WjBF
-MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
-ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB
-gQC2ZSzGIlv2zRsELkmEA1JcvIdsFv80b0NbBftewDAQRuyPlhGNifFx6v7+3O1F
-5+f+So43N0QbdrHu11K+ZuXNc6hUy0ofG/eRqXniGZEn8paUdQ98sWsbWelBDNeg
-WX4FQomynjyxbG+3IuJR5UHoLWhrJ9+pbPrT915eObbaTQIDAQABo1AwTjAdBgNV
-HQ4EFgQUGAhDu+gfckg4IkHRCQWBn4ltKV4wHwYDVR0jBBgwFoAUGAhDu+gfckg4
-IkHRCQWBn4ltKV4wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOBgQAaGx5U
-JRW5HC9dXADLf9OnmJRqWi3VNXEXWFk5XgHKc1z7KIBWMsdj+1gzm5ltRO7hkHw9
-bx6jQKZBRiUxyqTFw9Ywrk1fYFAxk8hxuqVYcGdonImJarHZTdVMBRWut9+EZBVm
-77eYbz2zASNpy++QIg85YgQum9uqREClHRBsxQ==
------END CERTIFICATE-----`,
-			Key: `-----BEGIN PRIVATE KEY-----
-MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBALZlLMYiW/bNGwQu
-SYQDUly8h2wW/zRvQ1sF+17AMBBG7I+WEY2J8XHq/v7c7UXn5/5Kjjc3RBt2se7X
-Ur5m5c1zqFTLSh8b95GpeeIZkSfylpR1D3yxaxtZ6UEM16BZfgVCibKePLFsb7ci
-4lHlQegtaGsn36ls+tP3Xl45ttpNAgMBAAECgYAAqZFmDs3isY/9jeV6c0CjUZP0
-UokOubC27eihyXTjOj61rsfVicC0tzPB3S+HZ3YyODcYAD1hFCdFRMbqJhmDiewK
-5GfATdNQeNARCfJdjYn57NKaXm7rc4C3so1YfxTL6k9QGJgTcybXiClQPDrhkZt3
-YLIeeJbY3OppLqjzgQJBAN5AzwyUqX5eQIUncQKcFY0PIjfFTku62brT7hq+TlqY
-1B6n3GUtIX+tyYg1qusy4KUUSzMslXJubHsxKanGqZ0CQQDSFwzK7KEYoZol5OMX
-mRsavc3iXmmEkkNRdNb1R4UqrlasPeeIeO1CfoD2RPcQhZCwFtR8xS8u6X9ncfC4
-qyxxAkAhpQvy6ppR7/Cyd4sLCxfUF8NlT/APVMTbHHQCBmcUHeiWj3C0vEVC78r/
-XKh4HGaXdt//ajNhdEflykZ1VgadAkB6Zh934mEA3rXWOgHsb7EQ5WAb8HF9YVGD
-FZVfFaoJ8cRhWTeZlQp14Qn1cLyYjZh8XvCxOJiCtlsZw5JBpMihAkBA6ltWb+aZ
-EBjC8ZRwZE+cAzmxaYPSs2J7JhS7X7H7Ax7ShhvHI4br3nqf00H4LkvtcHkn5d9G
-MwE1w2r4Deww
------END PRIVATE KEY-----`,
+			Domain:      "test.domain.com",
+			Certificate: CertificateFixture,
+			Key:         KeyFixture,
 		},
 	}
 
diff --git a/helpers_test.go b/helpers_test.go
index 5d564891d50a7392859a05aea2762960344960b8..3bab08e04375e1e309844776d2ba7c93493981b2 100644
--- a/helpers_test.go
+++ b/helpers_test.go
@@ -1,8 +1,17 @@
 package main
 
 import (
+	"crypto/tls"
+	"fmt"
+	"github.com/stretchr/testify/assert"
+	"io/ioutil"
 	"log"
+	"net"
+	"net/http"
 	"os"
+	"os/exec"
+	"testing"
+	"time"
 )
 
 var chdirSet = false
@@ -19,3 +28,166 @@ func setUpTests() {
 		chdirSet = true
 	}
 }
+
+// The HTTPS certificate isn't signed by anyone. This http client is set up
+// so it can talk to servers using it.
+var InsecureHTTPSClient = &http.Client{
+	Transport: &http.Transport{
+		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
+	},
+}
+
+var CertificateFixture = `-----BEGIN CERTIFICATE-----
+MIICWDCCAcGgAwIBAgIJAMyzCfoGEwVNMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
+BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
+aWRnaXRzIFB0eSBMdGQwHhcNMTYwMjExMTcxNzM2WhcNMjYwMjA4MTcxNzM2WjBF
+MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
+ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB
+gQC2ZSzGIlv2zRsELkmEA1JcvIdsFv80b0NbBftewDAQRuyPlhGNifFx6v7+3O1F
+5+f+So43N0QbdrHu11K+ZuXNc6hUy0ofG/eRqXniGZEn8paUdQ98sWsbWelBDNeg
+WX4FQomynjyxbG+3IuJR5UHoLWhrJ9+pbPrT915eObbaTQIDAQABo1AwTjAdBgNV
+HQ4EFgQUGAhDu+gfckg4IkHRCQWBn4ltKV4wHwYDVR0jBBgwFoAUGAhDu+gfckg4
+IkHRCQWBn4ltKV4wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOBgQAaGx5U
+JRW5HC9dXADLf9OnmJRqWi3VNXEXWFk5XgHKc1z7KIBWMsdj+1gzm5ltRO7hkHw9
+bx6jQKZBRiUxyqTFw9Ywrk1fYFAxk8hxuqVYcGdonImJarHZTdVMBRWut9+EZBVm
+77eYbz2zASNpy++QIg85YgQum9uqREClHRBsxQ==
+-----END CERTIFICATE-----`
+
+var KeyFixture = `-----BEGIN PRIVATE KEY-----
+MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBALZlLMYiW/bNGwQu
+SYQDUly8h2wW/zRvQ1sF+17AMBBG7I+WEY2J8XHq/v7c7UXn5/5Kjjc3RBt2se7X
+Ur5m5c1zqFTLSh8b95GpeeIZkSfylpR1D3yxaxtZ6UEM16BZfgVCibKePLFsb7ci
+4lHlQegtaGsn36ls+tP3Xl45ttpNAgMBAAECgYAAqZFmDs3isY/9jeV6c0CjUZP0
+UokOubC27eihyXTjOj61rsfVicC0tzPB3S+HZ3YyODcYAD1hFCdFRMbqJhmDiewK
+5GfATdNQeNARCfJdjYn57NKaXm7rc4C3so1YfxTL6k9QGJgTcybXiClQPDrhkZt3
+YLIeeJbY3OppLqjzgQJBAN5AzwyUqX5eQIUncQKcFY0PIjfFTku62brT7hq+TlqY
+1B6n3GUtIX+tyYg1qusy4KUUSzMslXJubHsxKanGqZ0CQQDSFwzK7KEYoZol5OMX
+mRsavc3iXmmEkkNRdNb1R4UqrlasPeeIeO1CfoD2RPcQhZCwFtR8xS8u6X9ncfC4
+qyxxAkAhpQvy6ppR7/Cyd4sLCxfUF8NlT/APVMTbHHQCBmcUHeiWj3C0vEVC78r/
+XKh4HGaXdt//ajNhdEflykZ1VgadAkB6Zh934mEA3rXWOgHsb7EQ5WAb8HF9YVGD
+FZVfFaoJ8cRhWTeZlQp14Qn1cLyYjZh8XvCxOJiCtlsZw5JBpMihAkBA6ltWb+aZ
+EBjC8ZRwZE+cAzmxaYPSs2J7JhS7X7H7Ax7ShhvHI4br3nqf00H4LkvtcHkn5d9G
+MwE1w2r4Deww
+-----END PRIVATE KEY-----`
+
+func CreateHTTPSFixtureFiles(t *testing.T) (key string, cert string) {
+	keyfile, err := ioutil.TempFile("", "https-fixture")
+	assert.NoError(t, err)
+	key = keyfile.Name()
+	keyfile.Close()
+
+	certfile, err := ioutil.TempFile("", "https-fixture")
+	assert.NoError(t, err)
+	cert = certfile.Name()
+	certfile.Close()
+
+	assert.NoError(t, ioutil.WriteFile(key, []byte(KeyFixture), 0644))
+	assert.NoError(t, ioutil.WriteFile(cert, []byte(CertificateFixture), 0644))
+
+	return keyfile.Name(), certfile.Name()
+}
+
+// ListenSpec is used to point at a gitlab-pages http server, preserving the
+// type of port it is (http, https, proxy)
+type ListenSpec struct {
+	Type string
+	Host string
+	Port string
+}
+
+func (l ListenSpec) URL(suffix string) string {
+	scheme := "http"
+	if l.Type == "https" {
+		scheme = "https"
+	}
+
+	return fmt.Sprintf("%s://%s/%s", scheme, l.JoinHostPort(), suffix)
+}
+
+// Returns only once this spec points at a working TCP server
+func (l ListenSpec) WaitUntilListening() {
+	for {
+		conn, _ := net.Dial("tcp", l.JoinHostPort())
+		if conn != nil {
+			conn.Close()
+			break
+		}
+	}
+}
+
+func (l ListenSpec) JoinHostPort() string {
+	return net.JoinHostPort(l.Host, l.Port)
+}
+
+// RunPagesProcess will start a gitlab-pages process with the specified listeners
+// and return a function you can call to shut it down again. Use
+// GetPageFromProcess to do a HTTP GET against a listener.
+//
+// If run as root via sudo, the gitlab-pages process will drop privileges
+func RunPagesProcess(t *testing.T, pagesPath string, listeners []ListenSpec) (teardown func()) {
+	var tempfiles []string
+	var args []string
+	var hasHTTPS bool
+
+	_, err := os.Stat(pagesPath)
+	assert.NoError(t, err)
+
+	for _, spec := range listeners {
+		args = append(args, "-listen-"+spec.Type, spec.JoinHostPort())
+
+		if spec.Type == "https" {
+			hasHTTPS = true
+		}
+	}
+
+	if hasHTTPS {
+		key, cert := CreateHTTPSFixtureFiles(t)
+		tempfiles = []string{key, cert}
+		args = append(args, "-root-key", key, "-root-cert", cert)
+	}
+
+	if os.Geteuid() == 0 && os.Getenv("SUDO_UID") != "" && os.Getenv("SUDO_GID") != "" {
+		t.Log("Pages process will drop privileges")
+		args = append(args, "-daemon-uid", os.Getenv("SUDO_UID"), "-daemon-gid", os.Getenv("SUDO_GID"))
+	}
+
+	cmd := exec.Command(pagesPath, args...)
+	t.Logf("Running %s %v", pagesPath, args)
+	cmd.Start()
+
+	// Wait for all TCP servers to be open. Even with this, gitlab-pages
+	// will sometimes return 404 if a HTTP request comes in before it has
+	// updated its set of domains. This usually takes < 1ms, hence the sleep
+	// for now. Without it, intermittent failures occur.
+	//
+	// TODO: replace this with explicit status from the pages binary
+	// TODO: fix the first-request race
+	for _, spec := range listeners {
+		spec.WaitUntilListening()
+	}
+	time.Sleep(50 * time.Millisecond)
+
+	return func() {
+		cmd.Process.Kill()
+		cmd.Process.Wait()
+		for _, tempfile := range tempfiles {
+			os.Remove(tempfile)
+		}
+	}
+}
+
+// Does an insecure HTTP GET against the listener specified, setting a fake
+// Host: and constructing the URL from the listener and the URL suffix.
+func GetPageFromListener(t *testing.T, spec ListenSpec, host, urlsuffix string) (*http.Response, error) {
+	url := spec.URL(urlsuffix)
+	req, err := http.NewRequest("GET", url, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	req.Host = host
+
+	t.Logf("curl -H'Host: %s' %s", host, url)
+
+	return InsecureHTTPSClient.Do(req)
+}
diff --git a/main.go b/main.go
index ba7a25db92dab612ea1f6d55b20040f5f05dde83..62d28e0e67fa6e9dc63c59c241a48d92f1227bb8 100644
--- a/main.go
+++ b/main.go
@@ -3,7 +3,6 @@ package main
 import (
 	"flag"
 	"log"
-	"net"
 	"os"
 	"strings"
 )
@@ -16,9 +15,12 @@ var REVISION = "HEAD"
 
 func appMain() {
 	var showVersion = flag.Bool("version", false, "Show version")
-	var listenHTTP = flag.String("listen-http", "", "The address to listen for HTTP requests")
-	var listenHTTPS = flag.String("listen-https", "", "The address to listen for HTTPS requests")
-	var listenProxy = flag.String("listen-proxy", "", "The address to listen for proxy requests")
+	var listenHTTP, listenHTTPS, listenProxy MultiStringFlag
+
+	flag.Var(&listenHTTP, "listen-http", "The address(es) to listen on for HTTP requests")
+	flag.Var(&listenHTTPS, "listen-https", "The address(es) to listen on for HTTPS requests")
+	flag.Var(&listenProxy, "listen-proxy", "The address(es) to listen on for proxy requests")
+
 	var pagesRootCert = flag.String("root-cert", "", "The default path to file certificate to serve static pages")
 	var pagesRootKey = flag.String("root-key", "", "The default path to file certificate to serve static pages")
 	var redirectHTTP = flag.Bool("redirect-http", true, "Serve the pages under HTTP")
@@ -53,22 +55,22 @@ func appMain() {
 		config.RootKey = readFile(*pagesRootKey)
 	}
 
-	if *listenHTTP != "" {
-		var l net.Listener
-		l, config.ListenHTTP = createSocket(*listenHTTP)
+	for _, addr := range listenHTTP {
+		l, fd := createSocket(addr)
 		defer l.Close()
+		config.ListenHTTP = append(config.ListenHTTP, fd)
 	}
 
-	if *listenHTTPS != "" {
-		var l net.Listener
-		l, config.ListenHTTPS = createSocket(*listenHTTPS)
+	for _, addr := range listenHTTPS {
+		l, fd := createSocket(addr)
 		defer l.Close()
+		config.ListenHTTPS = append(config.ListenHTTPS, fd)
 	}
 
-	if *listenProxy != "" {
-		var l net.Listener
-		l, config.ListenProxy = createSocket(*listenProxy)
+	for _, addr := range listenProxy {
+		l, fd := createSocket(addr)
 		defer l.Close()
+		config.ListenProxy = append(config.ListenProxy, fd)
 	}
 
 	if *daemonUID != 0 || *daemonGID != 0 {
diff --git a/multi_string_flag.go b/multi_string_flag.go
new file mode 100644
index 0000000000000000000000000000000000000000..ba6d92eaf740c7c01a27461ef7e5de691366fec4
--- /dev/null
+++ b/multi_string_flag.go
@@ -0,0 +1,22 @@
+package main
+
+import (
+	"strings"
+)
+
+// MultiStringFlag implements the flag.Value interface and allows a string flag
+// to be specified multiple times on the command line.
+//
+// e.g.: -listen-http 127.0.0.1:80 -listen-http [::1]:80
+type MultiStringFlag []string
+
+// String returns the list of parameters joined with a commas (",")
+func (s *MultiStringFlag) String() string {
+	return strings.Join(*s, ",")
+}
+
+// Set appends the value to the list of parameters
+func (s *MultiStringFlag) Set(value string) error {
+	*s = append(*s, value)
+	return nil
+}
diff --git a/multi_string_flag_test.go b/multi_string_flag_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..bbbdea3d3ae2ac35f61e9d44dc64da41fc4fecdf
--- /dev/null
+++ b/multi_string_flag_test.go
@@ -0,0 +1,19 @@
+package main
+
+import (
+	"flag"
+	"github.com/stretchr/testify/assert"
+	"testing"
+)
+
+func TestMultiStringFlagAppendsOnSet(t *testing.T) {
+	var concrete MultiStringFlag
+	var iface flag.Value
+
+	iface = &concrete
+
+	assert.NoError(t, iface.Set("foo"))
+	assert.NoError(t, iface.Set("bar"))
+
+	assert.Equal(t, MultiStringFlag{"foo", "bar"}, concrete)
+}