diff --git a/config/setup/git.go b/config/setup/git.go
index f25fbc22e..2e6c76caf 100644
--- a/config/setup/git.go
+++ b/config/setup/git.go
@@ -2,7 +2,6 @@ package setup
 
 import (
 	"fmt"
-	"log"
 	"net/url"
 	"path/filepath"
 	"runtime"
@@ -22,22 +21,8 @@ func Git(c *Controller) (middleware.Middleware, error) {
 	}
 
 	c.Startup = append(c.Startup, func() error {
-		// Startup functions are blocking; start
-		// service routine in background
-		go func() {
-			for {
-				time.Sleep(repo.Interval)
-
-				err := repo.Pull()
-				if err != nil {
-					if git.Logger == nil {
-						log.Println(err)
-					} else {
-						git.Logger.Println(err)
-					}
-				}
-			}
-		}()
+		// Start service routine in background
+		git.Start(repo)
 
 		// Do a pull right away to return error
 		return repo.Pull()
diff --git a/config/setup/git_test.go b/config/setup/git_test.go
index 3a441bbd4..cec0fe9bf 100644
--- a/config/setup/git_test.go
+++ b/config/setup/git_test.go
@@ -1,6 +1,9 @@
 package setup
 
 import (
+	"io/ioutil"
+	"log"
+	"strings"
 	"testing"
 	"time"
 
@@ -13,18 +16,83 @@ func init() {
 	git.SetOS(gittest.FakeOS)
 }
 
+func check(t *testing.T, err error) {
+	if err != nil {
+		t.Errorf("Expected no errors, but got: %v", err)
+	}
+}
+
 func TestGit(t *testing.T) {
 	c := newTestController(`git git@github.com:mholt/caddy.git`)
 
 	mid, err := Git(c)
-	if err != nil {
-		t.Errorf("Expected no errors, but got: %v", err)
-	}
+	check(t, err)
 	if mid != nil {
 		t.Fatal("Git middleware is a background service and expected to be nil.")
 	}
 }
 
+func TestIntervals(t *testing.T) {
+	tests := []string{
+		`git git@github.com:user/repo { interval 10 }`,
+		`git git@github.com:user/repo { interval 5 }`,
+		`git git@github.com:user/repo { interval 2 }`,
+		`git git@github.com:user/repo { interval 1 }`,
+		`git git@github.com:user/repo { interval 6 }`,
+	}
+
+	for i, test := range tests {
+		git.Logger = nil
+
+		c1 := newTestController(test)
+		repo, err := gitParse(c1)
+		check(t, err)
+
+		c2 := newTestController(test)
+		_, err = Git(c2)
+		check(t, err)
+
+		// start startup services
+		err = c2.Startup[0]()
+		check(t, err)
+
+		// wait for first background pull
+		time.Sleep(time.Millisecond * 100)
+
+		// switch logger to test file
+		logFile := gittest.Open("file")
+		git.Logger = log.New(logFile, "", 0)
+
+		// sleep for the interval
+		time.Sleep(repo.Interval)
+
+		// get log output
+		out, err := ioutil.ReadAll(logFile)
+		check(t, err)
+
+		// if greater than minimum interval
+		if repo.Interval >= time.Second*5 {
+			expected := `https://github.com/user/repo.git pulled.
+No new changes.`
+
+			// ensure pull is done by tracing the output
+			if expected != strings.TrimSpace(string(out)) {
+				t.Errorf("Test %v: Expected %v found %v", i, expected, string(out))
+			}
+		} else {
+			// ensure pull is ignored by confirming no output
+			if string(out) != "" {
+				t.Errorf("Test %v: Expected no output but found %v", i, string(out))
+			}
+		}
+
+		// stop background thread monitor
+		git.Monitor.StopAndWait(repo.URL, 1)
+
+	}
+
+}
+
 func TestGitParse(t *testing.T) {
 	tests := []struct {
 		input     string
diff --git a/middleware/git/git.go b/middleware/git/git.go
index d81421c9e..9cfa6565b 100644
--- a/middleware/git/git.go
+++ b/middleware/git/git.go
@@ -33,6 +33,9 @@ var initMutex = sync.Mutex{}
 // Logger is used to log errors; if nil, the default log.Logger is used.
 var Logger *log.Logger
 
+// Monitor listens for halt signal to stop repositories from auto pulling.
+var Monitor = &monitor{}
+
 // logger is an helper function to retrieve the available logger
 func logger() *log.Logger {
 	if Logger == nil {
diff --git a/middleware/git/git_test.go b/middleware/git/git_test.go
index 2d76e0bf4..23cd4969d 100644
--- a/middleware/git/git_test.go
+++ b/middleware/git/git_test.go
@@ -139,6 +139,39 @@ Command echo Hello successful.
 		}
 	}
 
+	// timeout checks
+	timeoutTests := []struct {
+		repo       *Repo
+		shouldPull bool
+	}{
+		{&Repo{Interval: time.Millisecond * 4900}, false},
+		{&Repo{Interval: time.Millisecond * 1}, false},
+		{&Repo{Interval: time.Second * 5}, true},
+		{&Repo{Interval: time.Second * 10}, true},
+	}
+
+	for i, r := range timeoutTests {
+		r.repo = createRepo(r.repo)
+
+		err := r.repo.Prepare()
+		check(t, err)
+		err = r.repo.Pull()
+		check(t, err)
+
+		before := r.repo.lastPull
+
+		time.Sleep(r.repo.Interval)
+
+		err = r.repo.Pull()
+		after := r.repo.lastPull
+		check(t, err)
+
+		expected := after.After(before)
+		if expected != r.shouldPull {
+			t.Errorf("Pull with Error %v: Expected %v found %v", i, expected, r.shouldPull)
+		}
+	}
+
 }
 
 func createRepo(r *Repo) *Repo {
diff --git a/middleware/git/service.go b/middleware/git/service.go
new file mode 100644
index 000000000..dacd14722
--- /dev/null
+++ b/middleware/git/service.go
@@ -0,0 +1,110 @@
+package git
+
+import (
+	"sync"
+	"time"
+)
+
+// RepoService is the repository service that runs in background and
+// periodic pull from the repository.
+type RepoService struct {
+	repo    *Repo
+	running bool          // whether service is running.
+	halt    chan struct{} // channel to notify service to halt and stop pulling.
+	exit    chan struct{} // channel to notify on exit.
+}
+
+// Start starts a new RepoService in background and adds it to monitor.
+func Start(repo *Repo) {
+	service := &RepoService{
+		repo,
+		true,
+		make(chan struct{}),
+		make(chan struct{}),
+	}
+
+	// start service
+	go func(s *RepoService) {
+		for {
+			// if service is halted
+			if !s.running {
+				// notify exit channel
+				service.exit <- struct{}{}
+				break
+			}
+			time.Sleep(repo.Interval)
+
+			err := repo.Pull()
+			if err != nil {
+				logger().Println(err)
+			}
+		}
+	}(service)
+
+	// add to monitor to enable halting
+	Monitor.add(service)
+}
+
+// monitor monitors running services (RepoService)
+// and can halt them.
+type monitor struct {
+	services []*RepoService
+	sync.Mutex
+}
+
+// add adds a new service to the monitor.
+func (m *monitor) add(service *RepoService) {
+	m.Lock()
+	defer m.Unlock()
+
+	m.services = append(m.services, service)
+
+	// start a goroutine to listen for halt signal
+	service.running = true
+	go func(r *RepoService) {
+		<-r.halt
+		r.running = false
+	}(service)
+}
+
+// Stop stops at most `limit` currently running services that is pulling from git repo at
+// repoURL. It returns list of exit channels for the services. A wait for message on the
+// channels guarantees exit. If limit is less than zero, it is ignored.
+// TODO find better ways to identify repos
+func (m *monitor) Stop(repoURL string, limit int) []chan struct{} {
+	m.Lock()
+	defer m.Unlock()
+
+	var chans []chan struct{}
+
+	// locate services
+	for i, j := 0, 0; i < len(m.services) && ((limit >= 0 && j < limit) || limit < 0); i++ {
+		s := m.services[i]
+		if s.repo.URL == repoURL {
+			// send halt signal
+			s.halt <- struct{}{}
+			chans = append(chans, s.exit)
+			j++
+			m.services[i] = nil
+		}
+	}
+
+	// remove them from services list
+	services := m.services[:0]
+	for _, s := range m.services {
+		if s != nil {
+			services = append(services, s)
+		}
+	}
+	m.services = services
+	return chans
+}
+
+// StopAndWait is similar to stop but it waits for the services to terminate before
+// returning.
+func (m *monitor) StopAndWait(repoUrl string, limit int) {
+	chans := m.Stop(repoUrl, limit)
+	for _, c := range chans {
+		<-c
+	}
+}
diff --git a/middleware/git/service_test.go b/middleware/git/service_test.go
new file mode 100644
index 000000000..114a58938
--- /dev/null
+++ b/middleware/git/service_test.go
@@ -0,0 +1,60 @@
+package git
+
+import (
+	"fmt"
+	"testing"
+	"time"
+
+	"github.com/mholt/caddy/middleware/git/gittest"
+)
+
+func init() {
+	SetOS(gittest.FakeOS)
+}
+
+func Test(t *testing.T) {
+	repo := &Repo{URL: "git@github.com", Interval: time.Second}
+
+	Start(repo)
+	if len(Monitor.services) != 1 {
+		t.Errorf("Expected 1 service, found %v", len(Monitor.services))
+	}
+
+	Monitor.StopAndWait(repo.URL, 1)
+	if len(Monitor.services) != 0 {
+		t.Errorf("Expected 1 service, found %v", len(Monitor.services))
+	}
+
+	repos := make([]*Repo, 5)
+	for i := 0; i < 5; i++ {
+		repos[i] = &Repo{URL: fmt.Sprintf("test%v", i), Interval: time.Second * 2}
+		Start(repos[i])
+		if len(Monitor.services) != i+1 {
+			t.Errorf("Expected %v service(s), found %v", i+1, len(Monitor.services))
+		}
+	}
+
+	time.Sleep(time.Second * 5)
+	Monitor.StopAndWait(repos[0].URL, 1)
+	if len(Monitor.services) != 4 {
+		t.Errorf("Expected %v service(s), found %v", 4, len(Monitor.services))
+	}
+
+	repo = &Repo{URL: "git@github.com", Interval: time.Second}
+	Start(repo)
+	if len(Monitor.services) != 5 {
+		t.Errorf("Expected %v service(s), found %v", 5, len(Monitor.services))
+	}
+
+	repo = &Repo{URL: "git@github.com", Interval: time.Second * 2}
+	Start(repo)
+	if len(Monitor.services) != 6 {
+		t.Errorf("Expected %v service(s), found %v", 6, len(Monitor.services))
+	}
+
+	time.Sleep(time.Second * 5)
+	Monitor.StopAndWait(repo.URL, -1)
+	if len(Monitor.services) != 4 {
+		t.Errorf("Expected %v service(s), found %v", 4, len(Monitor.services))
+	}
+}