diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7a4ff23a696cb8af40d33ed8f0011378e26d3ec5..b2e0fbd756fca113b6992c387ef37201640a5a81 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,7 +10,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 ### Added
 
 * Added support for [MSC2448](https://github.com/matrix-org/matrix-doc/pull/2448).
-* Added support for a `forKinds: ["all"]` option on datastores.
 * Added support for specifying a `region` to the S3 provider.
 * Pass-through the `Accept-Language` header for URL previews, with options to set a default.
 * Experimental support for IPFS.
@@ -23,6 +22,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 
 * Updated to Go 1.14
 
+## [1.0.2] - March 3, 2020
+
+### Added
+
+* Added support for a `forKinds: ["all"]` option on datastores.
+
+### Fixed
+
+* Fixed a bug with the cache where it would never expire old entries unless it was pressed for space.
+* Fixed a bug with the cache where the minimum cache time trigger would not work.
+
 ## [1.0.1] - February 27, 2020
 
 ### Fixed
@@ -67,7 +77,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 * Various other features that would be expected like maximum/minimum size controls, rate limiting, etc. Check out the
   sample config for a better idea of what else is possible.
 
-[unreleased]: https://github.com/turt2live/matrix-media-repo/compare/v1.0.1...HEAD
+[unreleased]: https://github.com/turt2live/matrix-media-repo/compare/v1.0.2...HEAD
+[1.0.2]: https://github.com/turt2live/matrix-media-repo/compare/v1.0.1...v1.0.2
 [1.0.1]: https://github.com/turt2live/matrix-media-repo/compare/v1.0.0...v1.0.1
 [1.0.0]: https://github.com/turt2live/matrix-media-repo/compare/v1.0.0-rc.2...v1.0.0
 [1.0.0-rc.2]: https://github.com/turt2live/matrix-media-repo/compare/v1.0.0-rc.1...v1.0.0-rc.2
diff --git a/cmd/media_repo/main.go b/cmd/media_repo/main.go
index d26fc9b62c473c8bdb468e4d5e01d9a540d9c9b7..3419a384ee4de3a2244d484192b60f8f6a16451c 100644
--- a/cmd/media_repo/main.go
+++ b/cmd/media_repo/main.go
@@ -12,6 +12,7 @@ import (
 	"github.com/turt2live/matrix-media-repo/common/logging"
 	"github.com/turt2live/matrix-media-repo/common/runtime"
 	"github.com/turt2live/matrix-media-repo/common/version"
+	"github.com/turt2live/matrix-media-repo/internal_cache"
 	"github.com/turt2live/matrix-media-repo/metrics"
 	"github.com/turt2live/matrix-media-repo/tasks"
 )
@@ -73,6 +74,7 @@ func main() {
 
 		logrus.Info("Stopping recurring tasks...")
 		tasks.StopAll()
+		internal_cache.Get().Stop()
 	}
 
 	// Set up a listener for SIGINT
diff --git a/config.sample.yaml b/config.sample.yaml
index 382a4a9a43b319b6641faff512dd894611fc15f8..4163993db9d1b01674e81405d6c6988dbc130fd9 100644
--- a/config.sample.yaml
+++ b/config.sample.yaml
@@ -191,7 +191,7 @@ downloads:
   failureCacheMinutes: 5
 
   # The cache control settings for downloads. This can help speed up downloads for users by
-  # keeping popular media in the cache.
+  # keeping popular media in the cache. This cache is also used for thumbnails.
   cache:
     enabled: true
 
diff --git a/controllers/download_controller/download_controller.go b/controllers/download_controller/download_controller.go
index 20d08a7c8545ced013efb63cd47f8ea6eaffa529..6eaea8749c037192121d6543a2c1c91d36d57562 100644
--- a/controllers/download_controller/download_controller.go
+++ b/controllers/download_controller/download_controller.go
@@ -98,7 +98,6 @@ func GetMedia(origin string, mediaId string, downloadRemote bool, blockForMedia
 			}
 
 			localCache.Set(origin+"/"+mediaId, media, cache.DefaultExpiration)
-			internal_cache.Get().IncrementDownloads(media.Sha256Hash)
 
 			cached, err := internal_cache.Get().GetMedia(media, ctx)
 			if err != nil {
diff --git a/controllers/thumbnail_controller/thumbnail_controller.go b/controllers/thumbnail_controller/thumbnail_controller.go
index ee2ee86a33ec06bf91083ae4cd9077a17947bcac..0858ad44bf9901e9d05b4ca668a2a2a51879d306 100644
--- a/controllers/thumbnail_controller/thumbnail_controller.go
+++ b/controllers/thumbnail_controller/thumbnail_controller.go
@@ -159,7 +159,7 @@ func GetThumbnail(origin string, mediaId string, desiredWidth int, desiredHeight
 			}, nil
 		}
 
-		ctx.Log.Info("Reading thumbnail from disk")
+		ctx.Log.Info("Reading thumbnail from datastore")
 		mediaStream, err := datastore.DownloadStream(ctx, thumbnail.DatastoreId, thumbnail.Location)
 		if err != nil {
 			return nil, err
diff --git a/internal_cache/media_cache.go b/internal_cache/media_cache.go
index a3e7a248d7f0a365401b885243e2bc0f02aec8f3..709e5078f9cab5f2e784be9f4ceac5fea1af39c3 100644
--- a/internal_cache/media_cache.go
+++ b/internal_cache/media_cache.go
@@ -27,6 +27,7 @@ type MediaCache struct {
 	tracker       *download_tracker.DownloadTracker
 	size          int64
 	enabled       bool
+	cleanupTimer  *time.Ticker
 }
 
 type cachedFile struct {
@@ -63,7 +64,19 @@ func Get() *MediaCache {
 				cache:         cache.New(trackedMinutes, trackedMinutes*2),
 				cooldownCache: cache.New(maxCooldown*2, maxCooldown*2),
 				tracker:       download_tracker.New(config.Get().Downloads.Cache.TrackedMinutes),
+				cleanupTimer:  time.NewTicker(5 * time.Minute),
 			}
+
+			go func() {
+				rctx := rcontext.Initial().LogWithFields(logrus.Fields{"task": "cache_cleanup"})
+				for _ = range instance.cleanupTimer.C {
+					rctx.Log.Info("Cache cleanup timer fired")
+					maxSize := config.Get().Downloads.Cache.MaxSizeBytes
+
+					b := instance.clearSpace(maxSize, math.MaxInt32, maxSize, true, rctx)
+					rctx.Log.Infof("Cleared %d bytes from cache during cleanup (%d bytes remain)", b, instance.size)
+				}
+			}()
 		}
 	})
 
@@ -82,6 +95,10 @@ func (c *MediaCache) Reset() {
 	c.tracker.Reset()
 }
 
+func (c *MediaCache) Stop() {
+	c.cleanupTimer.Stop()
+}
+
 func (c *MediaCache) IncrementDownloads(fileHash string) {
 	if !c.enabled {
 		return
@@ -186,7 +203,7 @@ func (c *MediaCache) updateItemInCache(recordId string, mediaSize int64, cacheFn
 		// We need to clean up some space
 		maxSizeClear := int64(math.Ceil(float64(mediaSize) * 1.25))
 		ctx.Log.Info(fmt.Sprintf("Attempting to clear %d bytes from media cache (max evict size %d bytes)", mediaSize, maxSizeClear))
-		clearedSpace := c.clearSpace(mediaSize, downloads, maxSizeClear, ctx)
+		clearedSpace := c.clearSpace(mediaSize, downloads, maxSizeClear, false, ctx)
 		ctx.Log.Info(fmt.Sprintf("Cleared %d bytes from media cache", clearedSpace))
 		freeSpace += clearedSpace
 		if freeSpace >= mediaSize {
@@ -215,7 +232,7 @@ func (c *MediaCache) updateItemInCache(recordId string, mediaSize int64, cacheFn
 				// set the maximum file size that can be cleared to the size of the cache which
 				// essentially allows us to remove anything.
 				downloadsLessThan := config.Get().Downloads.Cache.MinDownloads * 4
-				overageCleared := c.clearSpace(overage, downloadsLessThan, maxSpace, ctx) // metrics handled internally
+				overageCleared := c.clearSpace(overage, downloadsLessThan, maxSpace, true, ctx) // metrics handled internally
 				ctx.Log.Infof("Cleared %d bytes from media cache", overageCleared)
 			}
 
@@ -235,7 +252,7 @@ func (c *MediaCache) updateItemInCache(recordId string, mediaSize int64, cacheFn
 	return nil, nil
 }
 
-func (c *MediaCache) clearSpace(neededBytes int64, withDownloadsLessThan int, withSizeLessThan int64, ctx rcontext.RequestContext) int64 {
+func (c *MediaCache) clearSpace(neededBytes int64, withDownloadsLessThan int, withSizeLessThan int64, deleteEvenIfNotEnough bool, ctx rcontext.RequestContext) int64 {
 	// This should never happen, but we'll protect against it anyways. If we clear negative space we
 	// end up assuming that a very small amount being cleared is enough space for the file we're about
 	// to put in, which results in the cache growing beyond the file size limit.
@@ -253,9 +270,6 @@ func (c *MediaCache) clearSpace(neededBytes int64, withDownloadsLessThan int, wi
 	var preppedSpace int64 = 0
 	for k, item := range c.cache.Items() {
 		record := item.Object.(*cachedFile)
-		if int64(record.Contents.Len()) >= withSizeLessThan {
-			continue // file too large, cannot evict
-		}
 
 		var recordId string
 		if record.thumbnail != nil {
@@ -264,6 +278,10 @@ func (c *MediaCache) clearSpace(neededBytes int64, withDownloadsLessThan int, wi
 			recordId = record.media.Sha256Hash
 		}
 
+		if int64(record.Contents.Len()) >= withSizeLessThan {
+			continue // file too large, cannot evict
+		}
+
 		downloads := c.tracker.NumDownloads(recordId)
 		if downloads >= withDownloadsLessThan {
 			continue // too many downloads, cannot evict
@@ -281,7 +299,7 @@ func (c *MediaCache) clearSpace(neededBytes int64, withDownloadsLessThan int, wi
 		}
 	}
 
-	if preppedSpace < neededBytes {
+	if preppedSpace < neededBytes && !deleteEvenIfNotEnough {
 		// not enough space prepared - don't evict anything
 		return 0
 	}
@@ -346,14 +364,14 @@ func (c *MediaCache) checkExpiration(cd *cooldown, recordId string) bool {
 
 func (c *MediaCache) flagEvicted(recordId string) {
 	logrus.Info("Flagging " + recordId + " as evicted (overwriting any previous cooldowns)")
-	duration := int64(config.Get().Downloads.Cache.MinEvictedTimeSeconds) * 1000
-	c.cooldownCache.Set(recordId, &cooldown{isEviction: true, expiresTs: duration}, cache.DefaultExpiration)
+	expireTs := (int64(config.Get().Downloads.Cache.MinEvictedTimeSeconds) * 1000) + util.NowMillis()
+	c.cooldownCache.Set(recordId, &cooldown{isEviction: true, expiresTs: expireTs}, cache.DefaultExpiration)
 }
 
 func (c *MediaCache) flagCached(recordId string) {
 	logrus.Info("Flagging " + recordId + " as joining the cache (overwriting any previous cooldowns)")
-	duration := int64(config.Get().Downloads.Cache.MinCacheTimeSeconds) * 1000
-	c.cooldownCache.Set(recordId, &cooldown{isEviction: false, expiresTs: duration}, cache.DefaultExpiration)
+	expireTs := (int64(config.Get().Downloads.Cache.MinCacheTimeSeconds) * 1000) + util.NowMillis()
+	c.cooldownCache.Set(recordId, &cooldown{isEviction: false, expiresTs: expireTs}, cache.DefaultExpiration)
 }
 
 func (c *cooldown) IsExpired() bool {