diff --git a/src/libexpr/primops/flake.cc b/src/libexpr/primops/flake.cc
index 7cbbf9e99..257b81887 100644
--- a/src/libexpr/primops/flake.cc
+++ b/src/libexpr/primops/flake.cc
@@ -265,6 +265,7 @@ static SourceInfo fetchFlake(EvalState & state, const FlakeRef & flakeRef, bool
         request.unpack = true;
         request.name = "source";
         request.ttl = resolvedRef.rev ? 1000000000 : settings.tarballTtl;
+        request.getLastModified = true;
         auto result = getDownloader()->downloadCached(state.store, request);
 
         if (!result.etag)
@@ -278,6 +279,7 @@ static SourceInfo fetchFlake(EvalState & state, const FlakeRef & flakeRef, bool
         SourceInfo info(ref);
         info.storePath = result.storePath;
         info.narHash = state.store->queryPathInfo(info.storePath)->narHash;
+        info.lastModified = result.lastModified;
 
         return info;
     }
diff --git a/src/libstore/download.cc b/src/libstore/download.cc
index 0d1974d3b..0338727c1 100644
--- a/src/libstore/download.cc
+++ b/src/libstore/download.cc
@@ -808,6 +808,7 @@ CachedDownloadResult Downloader::downloadCached(
             CachedDownloadResult result;
             result.storePath = expectedStorePath;
             result.path = store->toRealPath(expectedStorePath);
+            assert(!request.getLastModified); // FIXME
             return result;
         }
     }
@@ -892,16 +893,26 @@ CachedDownloadResult Downloader::downloadCached(
             store->addTempRoot(unpackedStorePath);
             if (!store->isValidPath(unpackedStorePath))
                 unpackedStorePath = "";
+            else
+                result.lastModified = lstat(unpackedLink).st_mtime;
         }
         if (unpackedStorePath.empty()) {
             printInfo(format("unpacking '%1%'...") % url);
             Path tmpDir = createTempDir();
             AutoDelete autoDelete(tmpDir, true);
             // FIXME: this requires GNU tar for decompression.
-            runProgram("tar", true, {"xf", store->toRealPath(storePath), "-C", tmpDir, "--strip-components", "1"});
-            unpackedStorePath = store->addToStore(name, tmpDir, true, htSHA256, defaultPathFilter, NoRepair);
+            runProgram("tar", true, {"xf", store->toRealPath(storePath), "-C", tmpDir});
+            auto members = readDirectory(tmpDir);
+            if (members.size() != 1)
+                throw nix::Error("tarball '%s' contains an unexpected number of top-level files", url);
+            auto topDir = tmpDir + "/" + members.begin()->name;
+            result.lastModified = lstat(topDir).st_mtime;
+            unpackedStorePath = store->addToStore(name, topDir, true, htSHA256, defaultPathFilter, NoRepair);
         }
-        replaceSymlink(unpackedStorePath, unpackedLink);
+        // Store the last-modified date of the tarball in the symlink
+        // mtime. This saves us from having to store it somewhere
+        // else.
+        replaceSymlink(unpackedStorePath, unpackedLink, result.lastModified);
         storePath = unpackedStorePath;
     }
 
diff --git a/src/libstore/download.hh b/src/libstore/download.hh
index 404e51195..43b1c5c09 100644
--- a/src/libstore/download.hh
+++ b/src/libstore/download.hh
@@ -49,6 +49,7 @@ struct CachedDownloadRequest
     Hash expectedHash;
     unsigned int ttl = settings.tarballTtl;
     bool gcRoot = false;
+    bool getLastModified = false;
 
     CachedDownloadRequest(const std::string & uri)
         : uri(uri) { }
@@ -62,6 +63,7 @@ struct CachedDownloadResult
     Path path;
     std::optional<std::string> etag;
     std::string effectiveUri;
+    std::optional<time_t> lastModified;
 };
 
 class Store;
diff --git a/src/libutil/util.cc b/src/libutil/util.cc
index f82f902fc..92c8957ff 100644
--- a/src/libutil/util.cc
+++ b/src/libutil/util.cc
@@ -22,6 +22,7 @@
 #include <sys/ioctl.h>
 #include <sys/types.h>
 #include <sys/wait.h>
+#include <sys/time.h>
 #include <unistd.h>
 
 #ifdef __APPLE__
@@ -552,20 +553,31 @@ Paths createDirs(const Path & path)
 }
 
 
-void createSymlink(const Path & target, const Path & link)
+void createSymlink(const Path & target, const Path & link,
+    std::optional<time_t> mtime)
 {
     if (symlink(target.c_str(), link.c_str()))
         throw SysError(format("creating symlink from '%1%' to '%2%'") % link % target);
+    if (mtime) {
+        struct timeval times[2];
+        times[0].tv_sec = *mtime;
+        times[0].tv_usec = 0;
+        times[1].tv_sec = *mtime;
+        times[1].tv_usec = 0;
+        if (lutimes(link.c_str(), times))
+            throw SysError("setting time of symlink '%s'", link);
+    }
 }
 
 
-void replaceSymlink(const Path & target, const Path & link)
+void replaceSymlink(const Path & target, const Path & link,
+    std::optional<time_t> mtime)
 {
     for (unsigned int n = 0; true; n++) {
         Path tmp = canonPath(fmt("%s/.%d_%s", dirOf(link), n, baseNameOf(link)));
 
         try {
-            createSymlink(target, tmp);
+            createSymlink(target, tmp, mtime);
         } catch (SysError & e) {
             if (e.errNo == EEXIST) continue;
             throw;
diff --git a/src/libutil/util.hh b/src/libutil/util.hh
index 35f9169f6..e05ef1e7d 100644
--- a/src/libutil/util.hh
+++ b/src/libutil/util.hh
@@ -142,10 +142,12 @@ Path getDataDir();
 Paths createDirs(const Path & path);
 
 /* Create a symlink. */
-void createSymlink(const Path & target, const Path & link);
+void createSymlink(const Path & target, const Path & link,
+    std::optional<time_t> mtime = {});
 
 /* Atomically create or replace a symlink. */
-void replaceSymlink(const Path & target, const Path & link);
+void replaceSymlink(const Path & target, const Path & link,
+    std::optional<time_t> mtime = {});
 
 
 /* Wrappers arount read()/write() that read/write exactly the