diff --git a/_test/conf/acl.auth.php b/_test/conf/acl.auth.php
index 8a1b01f23f1cebb240c74a7751f731a64fa96595..49574072002f6c3ce960f309a0b1b398e550a9a9 100644
--- a/_test/conf/acl.auth.php
+++ b/_test/conf/acl.auth.php
@@ -19,6 +19,7 @@
 # delete 16
 
 *               @ALL        8
+private:*       @ALL        0
 
 # for testing wildcards:
 users:*            @ALL         1
diff --git a/_test/tests/inc/media_ispublic.test.php b/_test/tests/inc/media_ispublic.test.php
new file mode 100644
index 0000000000000000000000000000000000000000..307c64654154c32e7ddf900c373fa0567dc1f12b
--- /dev/null
+++ b/_test/tests/inc/media_ispublic.test.php
@@ -0,0 +1,18 @@
+<?php
+
+class media_ispublic_test extends DokuWikiTest {
+
+
+    public function test_external(){
+        $this->assertTrue(media_ispublic('http://www.example.com/foo.png'));
+        $this->assertTrue(media_ispublic('https://www.example.com/foo.png'));
+        $this->assertTrue(media_ispublic('hTTp://www.example.com/foo.png'));
+        $this->assertTrue(media_ispublic('hTTps://www.example.com/foo.png'));
+    }
+
+    public function test_internal(){
+        $this->assertTrue(media_ispublic('wiki:logo.png'));
+        $this->assertFalse(media_ispublic('private:logo.png'));
+    }
+
+}
\ No newline at end of file
diff --git a/inc/media.php b/inc/media.php
index db1ca0d5786fcbc6290a91b46851cc140cc367d9..501d170f30a472d62e653d21c841eaf33ff2c2aa 100644
--- a/inc/media.php
+++ b/inc/media.php
@@ -82,6 +82,20 @@ function media_metasave($id,$auth,$data){
     }
 }
 
+/**
+ * Check if a media item is public (eg, external URL or readable by @ALL)
+ *
+ * @author Andreas Gohr <andi@splitbrain.org>
+ * @param string $id  the media ID or URL
+ * @return bool
+ */
+function media_ispublic($id){
+    if(preg_match('/^https?:\/\//i',$id)) return true;
+    $id = cleanID($id);
+    if(auth_aclcheck(getNS($id).':*', '', array()) >= AUTH_READ) return true;
+    return false;
+}
+
 /**
  * Display the form to edit image meta data
  *
diff --git a/lib/exe/fetch.php b/lib/exe/fetch.php
index 9bac4d272abd26b6dcff454457f10095681a929a..a9147a6c07a058b9478fcdc36895173f8943b0a8 100644
--- a/lib/exe/fetch.php
+++ b/lib/exe/fetch.php
@@ -47,6 +47,7 @@ if(!defined('SIMPLE_TEST')) {
         'height'        => $HEIGHT,
         'status'        => $STATUS,
         'statusmessage' => $STATUSMESSAGE,
+        'ispublic'      => media_ispublic($MEDIA),
     );
 
     // handle the file status
@@ -81,10 +82,13 @@ if(!defined('SIMPLE_TEST')) {
     // finally send the file to the client
     $evt = new Doku_Event('MEDIA_SENDFILE', $data);
     if($evt->advise_before()) {
-        sendFile($data['file'], $data['mime'], $data['download'], $data['cache']);
+        $cache = $data['cache'];
+        if($cache != 0 && !$data['ispublic']) $cache = 0; // no cache headers for private files FS#2734
+
+        sendFile($data['file'], $data['mime'], $data['download'], $cache);
     }
     // Do something after the download finished.
-    $evt->advise_after();
+    $evt->advise_after();  // will not be emitted on 304 or x-sendfile
 
 }// END DO main
 
@@ -93,8 +97,18 @@ if(!defined('SIMPLE_TEST')) {
 /**
  * Set headers and send the file to the client
  *
+ * Unless $cache is set to 0, the data may end up in intermediate proxy servers. Therefor,
+ * if you're sending (ACL protected) private files, $cache should be 0.
+ *
+ * This function will abort the current script when a 304 is sent or file sending is handled
+ * through x-sendfile
+ *
  * @author Andreas Gohr <andi@splitbrain.org>
  * @author Ben Coburn <btcoburn@silicodon.net>
+ * @param string $file  local file to send
+ * @param string $mime  mime type of the file
+ * @param bool   $dl    set to true to force a browser download
+ * @param int    $cache remaining cache time in seconds (-1 for $conf['cache'], 0 for off)
  */
 function sendFile($file, $mime, $dl, $cache) {
     global $conf;
@@ -115,9 +129,10 @@ function sendFile($file, $mime, $dl, $cache) {
         header('Cache-Control: public, proxy-revalidate, no-transform, max-age='.max($fmtime - time() + $conf['cachetime'] + 10, 0));
         header('Pragma: public');
     } else if($cache == 0) {
-        // nocache
-        header('Cache-Control: must-revalidate, no-transform, post-check=0, pre-check=0');
-        header('Pragma: public');
+        // nocache, avoid resending files from intermediate caches without revalidation FS#2734
+        header('Expires: Thu, 01 Jan 1970 00:00:00 GMT');
+        header('Cache-Control: private, no-transform, max-age=0');
+        header('Pragma: no-cache');
     }
     //send important headers first, script stops here if '304 Not Modified' response
     http_conditionalRequest($fmtime);