<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>File-Sharing – Serverküche</title><link>https://serverkueche.de/en/tags/file-sharing/</link><description>File-Sharing – Neueste Beiträge von Serverküche</description><generator>Hugo</generator><language>en-US</language><managingEditor>feedback@serverkueche.de (Serverküche)</managingEditor><webMaster>feedback@serverkueche.de (Serverküche)</webMaster><copyright>2026 Serverküche</copyright><lastBuildDate>Thu, 29 Oct 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://serverkueche.de/en/tags/file-sharing/index.xml" rel="self" type="application/rss+xml"/><item><title>Pingvin Share X: Sharing Large Files Without WeTransfer</title><link>https://serverkueche.de/en/tutorials/pingvin-share-file-sharing/</link><pubDate>Thu, 29 Oct 2026 00:00:00 +0000</pubDate><author>feedback@serverkueche.de (Serverküche)</author><guid>https://serverkueche.de/en/tutorials/pingvin-share-file-sharing/</guid><description>Host files yourself instead of uploading them: Pingvin Share X 2.0 behind Traefik, with expiry, password and view counter – plus the settings that matter.</description><content:encoded><![CDATA[<p>Sending an 800 MB video to a colleague – and using someone else&rsquo;s service that keeps the file for
as long as it likes. There is another way: an upload field on your own server, a link with an
expiry date, and afterwards the file is gone.</p>
<h2 id="what-are-we-building">What are we building?</h2>
<p>We run <strong>Pingvin Share X 2.0.0</strong> behind Traefik. You upload files, get a short link, and the share
can <strong>expire</strong>, demand a <strong>password</strong> and disappear after a set <strong>number of views</strong>. Whoever
receives the link needs no account and no app.</p>
<p>The second part matters just as much: an upload service on the open internet is an invitation when
it is configured wrong. So we switch off open registration, cap the file size, and take a look at
how the data actually sits on disk.</p>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-sky-300 bg-sky-50 dark:border-sky-800 dark:bg-sky-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">ℹ️</span>Why the X in the name
  </p>
  <div class="prose-kitchen text-sm">The original <strong>Pingvin Share</strong> by stonith404 has been <strong>archived since June 2025</strong> – the developer
is focusing on another project and recommends the fork <strong>Pingvin Share X</strong> as the maintained
continuation in his own README. That is what we use, in version 2.0.0 from 3 October
2026. If you find a guide online using
<code>stonith404/pingvin-share</code>, you are deploying unmaintained software – for a service that publicly
accepts uploads, that is a bad idea.</div>
</div>
<h2 id="requirements">Requirements</h2>
<ul>
<li><a href="/en/tutorials/traefik-reverse-proxy/">Traefik as a reverse proxy</a> with a working Let&rsquo;s Encrypt
resolver and a domain pointing at the server</li>
<li>Enough <strong>disk space</strong> for what you intend to share – files sit uncompressed on the server until
the share expires</li>
<li>A clear sense of who you send links to; more on that in step 3</li>
</ul>
<div class="not-prose my-6 overflow-hidden rounded-xl border border-paprika-200 bg-paprika-50 dark:border-paprika-800 dark:bg-paprika-900/20"
     data-track-content data-content-name="Affiliate-Box · /en/tutorials/pingvin-share-file-sharing/" data-content-piece="VPS 1000 G12.5">
  <div class="flex items-center justify-between border-b border-paprika-200 bg-paprika-100 px-4 py-1.5 text-xs font-semibold uppercase tracking-wide text-paprika-700 dark:border-paprika-800 dark:bg-paprika-900/40 dark:text-paprika-300">
    <span>🍳 Recommendation</span>
    <span title="Links marked with * are affiliate links.">Ad</span>
  </div>
  <div class="flex flex-col gap-4 p-4 sm:flex-row sm:items-center sm:justify-between">
    <div>
      <p class="text-lg font-bold text-slate-900 dark:text-white">VPS 1000 G12.5</p>
      <p class="mt-1 text-sm text-slate-600 dark:text-slate-300">4 vCores · 8 GB RAM · 128 GB SSD</p>
      <p class="mt-1 text-sm font-semibold text-paprika-700 dark:text-paprika-400">from €14.50/month</p>
      <p class="mt-2 text-sm text-slate-600 dark:text-slate-400">Pingvin Share X is frugal – disk space is what matters here, not CPU.</p>
    </div>
    <a href="https://www.netcup.com/en/server/vps/vps-1000-g12.5-iv-24m-eu?ref=44083#vps-1000-g12.5-iv-12m-eu" rel="sponsored noopener" target="_blank"
   data-track-event="Affiliate|netcup: Affiliate-Box|VPS 1000 G12.5 · {page}"
   class="inline-flex shrink-0 items-center justify-center rounded-lg bg-paprika-600 px-5 py-2.5 font-semibold text-white transition-colors hover:bg-paprika-700">
  Go to netcup →
</a>

  </div><div class="px-4 pb-4"><div class="not-prose my-3 rounded-lg border border-herb-500/40 bg-herb-50 px-3 py-2 text-sm text-slate-700 dark:bg-herb-900/20 dark:text-slate-200"
     data-voucher-api="vps-1000" data-voucher-msg-empty="No code is available for this plan right now – use the €5.95 code." data-voucher-msg-limit="No more codes today – use the €5.95 code or try again tomorrow." data-voucher-msg-error="The code couldn&#39;t be fetched right now – use the €5.95 code."
     data-voucher-hint="Empty the cart first – the code adds the server itself.">
  <p class="flex flex-wrap items-center gap-x-2 gap-y-1"><span>💶 <span class="font-semibold">1 month free</span>:</span>
    <button type="button" data-voucher-reveal data-copy-title="Click to copy"
            class="cursor-pointer rounded bg-white px-2 py-0.5 font-mono text-sm font-semibold text-herb-800 hover:ring-2 hover:ring-herb-500/40 dark:bg-slate-800 dark:text-herb-400">Show code</button>
    <button type="button" data-voucher-next hidden
            class="rounded border border-herb-500/40 px-2 py-0.5 text-xs text-herb-800 hover:bg-white dark:text-herb-400 dark:hover:bg-slate-800">another code</button>
  </p><p class="mt-1 text-xs text-slate-500 dark:text-slate-400" data-voucher-hint-target>
    Single use, valid for VPS 1000 G12.5.
    <a href="https://www.netcup.com/en/checkout/cart?ref=44083" rel="sponsored noopener" target="_blank"
       data-track-event="Affiliate|netcup: Gutschein einlösen|VPS 1000 G12.5 · {page}"
       class="font-medium text-herb-800 underline underline-offset-2 hover:text-herb-900 dark:text-herb-400">Redeem in the cart →</a>
  </p><p class="mt-2 flex flex-wrap items-center gap-x-2 gap-y-1 border-t border-herb-500/30 pt-2">
    <span>💶 <span class="font-semibold">€5.95 voucher</span> for new netcup customers:</span>
    <button type="button" data-voucher-code data-track-voucher="36nc17844976032"
            title="Click to copy" class="cursor-pointer rounded bg-white px-2 py-0.5 font-mono text-sm font-semibold text-herb-800 hover:ring-2 hover:ring-herb-500/40 dark:bg-slate-800 dark:text-herb-400">36nc17844976032</button>
    <span class="text-xs text-slate-500 dark:text-slate-400">always valid · not for domains or VPS Lite</span>
  </p><p class="mt-1 text-xs"><a href="/en/netcup-voucher/" class="font-medium text-herb-800 underline underline-offset-2 hover:text-herb-900 dark:text-herb-400">All netcup vouchers →</a></p>
</div></div>
</div>

<h2 id="step-by-step">Step by step</h2>
<h3 id="step-1-create-the-stack">Step 1: Create the stack</h3>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">mkdir -p /opt/pingvin/data/images <span class="o">&amp;&amp;</span> <span class="nb">cd</span> /opt/pingvin</span></span></code></pre></div>
</div>
<p>We create the subdirectory up front because Pingvin otherwise reaches into nothing on first start.</p>
<div class="sk-code">
  <span class="sk-code-head">YAML</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="c"># /opt/pingvin/compose.yaml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">pingvin</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">ghcr.io/smp46/pingvin-share-x:v2.0.0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">pingvin</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">TRUST_PROXY</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;true&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./data:/opt/app/backend/data:rw</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./data/images:/opt/app/frontend/public/img:rw</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">networks</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">proxy]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">labels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">traefik.enable=true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">traefik.http.routers.pingvin.rule=Host(`YOUR_DOMAIN`)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">traefik.http.routers.pingvin.entrypoints=websecure</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">traefik.http.routers.pingvin.tls.certresolver=le</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">traefik.http.services.pingvin.loadbalancer.server.port=3000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">networks</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">proxy</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">external</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span></span></span></code></pre></div>
</div>
<p><code>TRUST_PROXY: &quot;true&quot;</code> is the entry people overlook: without it, Pingvin logs every access with
Traefik&rsquo;s IP instead of the visitor&rsquo;s. If there is <strong>no</strong> proxy in front, the value has to stay
<code>false</code> – otherwise any client can claim whatever source IP it likes.</p>
<p>The image tag is pinned deliberately. The project also has a <code>:beta</code> channel – for a service that
accepts other people&rsquo;s uploads, the stable version is the right choice.</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker compose up -d
</span></span><span class="line"><span class="cl">docker compose ps</span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">NAME      STATUS
</span></span><span class="line"><span class="cl">pingvin   Up 25 seconds (healthy)</span></span></code></pre></div>
</div>
<p>The <code>(healthy)</code> comes from the bundled healthcheck – the container does not just report as started
but actually answers. A glance at the logs confirms a clean boot:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker compose logs --tail <span class="m">1</span></span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">LOG [NestApplication] Nest application successfully started</span></span></code></pre></div>
</div>
<p>On first start Pingvin creates the SQLite database before that and applies some 40 migrations; that
takes a few seconds. Optionally, Pingvin can have uploads scanned by a ClamAV container. For a private setup where only you upload, that is dispensable; as soon as strangers may
deposit files (see reverse shares below), it becomes worthwhile.</p>
<p>Check from the outside that the chain holds:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">curl -s -o /dev/null -w <span class="s2">&#34;%{http_code}\n&#34;</span> https://YOUR_DOMAIN/</span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">200</span></span></code></pre></div>
</div>
<h3 id="step-2-create-the-first-account--then-close-the-door">Step 2: Create the first account – then close the door</h3>
<p>Open <code>https://YOUR_DOMAIN/auth/signUp</code>. <strong>The first registered user automatically becomes
administrator</strong> – there is no default password anyone could forget to change.</p>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-paprika-400 bg-paprika-50 dark:border-paprika-700 dark:bg-paprika-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">🔥</span>Keep reading right now
  </p>
  <div class="prose-kitchen text-sm">Registration is <strong>open</strong> out of the box. Between your own signup and the next step, anyone can
create an account on your server and consume disk space. Do the next step immediately, not
tomorrow.</div>
</div>
<h3 id="step-3-the-settings-that-actually-matter">Step 3: The settings that actually matter</h3>
<p>Under <strong>Configuration → General</strong> you first enter the <strong>App URL</strong>, that is <code>https://YOUR_DOMAIN</code>.
This is not cosmetic: Pingvin builds the links you send out from this value. If it still says the
default <code>http://localhost:3000</code>, your recipients get links that point nowhere for them.</p>
<p>The more important part is under <strong>Configuration → Security &amp; Access</strong>:</p>
<p><figure class="my-6"><img src="/en/tutorials/pingvin-share-file-sharing/pv-config-security_hu_e372e7f5fc49fe51.webp" srcset="/en/tutorials/pingvin-share-file-sharing/pv-config-security_hu_d56e89f3b20cf024.webp 480w, /en/tutorials/pingvin-share-file-sharing/pv-config-security_hu_e372e7f5fc49fe51.webp 768w, /en/tutorials/pingvin-share-file-sharing/pv-config-security_hu_b8d56f746386eceb.webp 1200w, /en/tutorials/pingvin-share-file-sharing/pv-config-security_hu_466407bf4b17acb6.webp 1920w" sizes="(min-width: 768px) 768px, 100vw"
    width="768" height="432"
    data-full="/en/tutorials/pingvin-share-file-sharing/pv-config-security_hu_3ba8a0cb57720470.webp"
    alt="The &ldquo;Security &amp; Access&rdquo; settings page of Pingvin Share X with Secure cookies switched on and registration switched off." title="Secure cookies on, Allow registration off – the two switches that matter"
    loading="lazy" decoding="async" class="rounded-lg"><figcaption class="mt-2 text-sm text-center text-slate-500 italic">Secure cookies on, Allow registration off – the two switches that matter</figcaption></figure></p>
<ul>
<li><strong>Allow registration</strong> – off. You are the only one creating shares. Afterwards <code>/auth/signUp</code>
only redirects, and the API rejects new accounts too (&ldquo;Registration is not allowed&rdquo;).</li>
<li><strong>Secure cookies</strong> – on. That makes Pingvin set the <code>Secure</code> flag on its cookies, so they are
never transmitted over unencrypted HTTP. This only works because Traefik delivers proper HTTPS in
front.</li>
<li><strong>Allow unauthenticated shares</strong> – off by default, and it stays that way. Otherwise any visitor
can drop files on your server.</li>
</ul>
<p>Every page has its own <strong>Save</strong> button, so save before switching. Under <strong>Configuration → Share</strong>
there are two more values that deserve a conscious decision:</p>
<ul>
<li><strong>Default share ID length</strong> – default 8. Pingvin says so itself: &ldquo;A value below 8 is not
considered secure.&rdquo; The link <strong>is</strong> the password as long as you set none; shorter IDs can be
brute-forced.</li>
<li><strong>Max size</strong> – default 1 GB. Set it to something your disk can take, and remember that several
shares can be open at the same time.</li>
</ul>
<p>Also interesting is the <strong>Chunk size</strong> (default 10 MB): Pingvin uploads large files in pieces. That
is why multi-gigabyte uploads work through a reverse proxy without you having to touch timeouts or
size limits.</p>
<h3 id="step-4-a-share-with-expiry-password-and-view-limit">Step 4: A share with expiry, password and view limit</h3>
<p>On <strong>Upload</strong> you drag a file into the field – the dialog opens right after:</p>
<p><figure class="my-6"><img src="/en/tutorials/pingvin-share-file-sharing/pv-share-optionen_hu_bc6fb25dc1cf8bf8.webp" srcset="/en/tutorials/pingvin-share-file-sharing/pv-share-optionen_hu_2bc02ce40fe2d238.webp 480w, /en/tutorials/pingvin-share-file-sharing/pv-share-optionen_hu_bc6fb25dc1cf8bf8.webp 768w, /en/tutorials/pingvin-share-file-sharing/pv-share-optionen_hu_48a963106ee5a58c.webp 1200w, /en/tutorials/pingvin-share-file-sharing/pv-share-optionen_hu_95864d891e9e9629.webp 1920w" sizes="(min-width: 768px) 768px, 100vw"
    width="768" height="432"
    data-full="/en/tutorials/pingvin-share-file-sharing/pv-share-optionen_hu_f3261113fb2c238a.webp"
    alt="The &ldquo;Create Share&rdquo; dialog with a generated link ID, seven days of expiry, a password set and a view limit of five." title="Three independent brakes: expiry date, password and maximum views"
    loading="lazy" decoding="async" class="rounded-lg"><figcaption class="mt-2 text-sm text-center text-slate-500 italic">Three independent brakes: expiry date, password and maximum views</figcaption></figure></p>
<p>The link ID is generated randomly; <strong>Generate</strong> gives you a new one, and you can replace it with
something meaningful – just not with something guessable. Below it are three limits that work
independently of each other:</p>
<ul>
<li><strong>Expiration</strong> – once it passes, the share is gone; seven days by default. &ldquo;Permanent share&rdquo;
exists too; use it sparingly, or your disk fills up with things nobody remembers.</li>
<li><strong>Password protection</strong> – an additional password. Send it over a <strong>different channel</strong> than the
link, otherwise it is theatre.</li>
<li><strong>Maximum views</strong> – after the configured number of views the share closes. Handy for &ldquo;exactly
once, exactly this person&rdquo;.</li>
</ul>
<p>A click on <strong>Share</strong> creates it and shows the finished link along with the expiry date.</p>
<h3 id="step-5-the-recipients-view--and-the-counter-test">Step 5: The recipient&rsquo;s view – and the counter-test</h3>
<p>This is what someone sees who opens the link without the password:</p>
<p><figure class="my-6"><img src="/en/tutorials/pingvin-share-file-sharing/pv-empfaenger-passwort_hu_d04d4942bc60580e.webp" srcset="/en/tutorials/pingvin-share-file-sharing/pv-empfaenger-passwort_hu_2fb8499375e9b481.webp 480w, /en/tutorials/pingvin-share-file-sharing/pv-empfaenger-passwort_hu_d04d4942bc60580e.webp 768w, /en/tutorials/pingvin-share-file-sharing/pv-empfaenger-passwort_hu_13b261030214d109.webp 1200w, /en/tutorials/pingvin-share-file-sharing/pv-empfaenger-passwort_hu_78359889a77f279b.webp 1920w" sizes="(min-width: 768px) 768px, 100vw"
    width="768" height="432"
    data-full="/en/tutorials/pingvin-share-file-sharing/pv-empfaenger-passwort_hu_971437b3cd7dec9e.webp"
    alt="The recipient page of Pingvin Share X with a &ldquo;Password required&rdquo; dialog above the greyed-out file list." title="Without the password the file list stays hidden"
    loading="lazy" decoding="async" class="rounded-lg"><figcaption class="mt-2 text-sm text-center text-slate-500 italic">Without the password the file list stays hidden</figcaption></figure></p>
<p>That this is not merely the interface is shown by the API. Query the share directly, without a
password:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">curl -s https://YOUR_DOMAIN/api/shares/YOUR_SHARE_ID</span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">{&#34;message&#34;:&#34;This share is password protected&#34;,&#34;error&#34;:&#34;share_password_required&#34;,&#34;statusCode&#34;:403}</span></span></code></pre></div>
</div>
<p>No file name, no size, no listing – the server gives away nothing before the password. After
entering it, the content appears:</p>
<p><figure class="my-6"><img src="/en/tutorials/pingvin-share-file-sharing/pv-empfaenger-dateien_hu_c35bf89a6066241a.webp" srcset="/en/tutorials/pingvin-share-file-sharing/pv-empfaenger-dateien_hu_70eecd107eae9310.webp 480w, /en/tutorials/pingvin-share-file-sharing/pv-empfaenger-dateien_hu_c35bf89a6066241a.webp 768w, /en/tutorials/pingvin-share-file-sharing/pv-empfaenger-dateien_hu_1d32635d75161fb8.webp 1200w, /en/tutorials/pingvin-share-file-sharing/pv-empfaenger-dateien_hu_a2de7f3a5ea3686d.webp 1920w" sizes="(min-width: 768px) 768px, 100vw"
    width="768" height="432"
    data-full="/en/tutorials/pingvin-share-file-sharing/pv-empfaenger-dateien_hu_821a1e30bc2ae5be.webp"
    alt="The recipient page with a file named menue-oktober.txt at 177 bytes, plus buttons to copy the link, preview and download." title="After the password: copy the link, preview or download"
    loading="lazy" decoding="async" class="rounded-lg"><figcaption class="mt-2 text-sm text-center text-slate-500 italic">After the password: copy the link, preview or download</figcaption></figure></p>
<p>Individual files can be downloaded directly; several are packed into a ZIP on request.</p>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-amber-400 bg-amber-50 dark:border-amber-700 dark:bg-amber-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">⚠️</span>The files are stored in the clear
  </p>
  <div class="prose-kitchen text-sm">The password protects the <strong>link</strong>, not the <strong>storage</strong>. On the server the files sit unencrypted
under <code>data/uploads/shares/&lt;share-id&gt;/&lt;uuid&gt;</code>; a text file there can be read with <code>cat</code>.
Anyone with access to the server or to your backup reads everything. For
genuinely confidential content, encrypt the file yourself beforehand, for instance as a
password-protected archive.</div>
</div>
<h3 id="step-6-reverse-shares--when-others-should-send-you-something">Step 6: Reverse shares – when others should send you something</h3>
<p>The way back is the genuinely underrated part. Under <strong>Reverse shares</strong> in your account, one click
on <strong>Create</strong> produces an upload link shaped like <code>https://YOUR_DOMAIN/upload/SOME_ID</code>. You send it
to someone, that person uploads – no account, no login – and the resulting share lands with you.</p>
<p>The dialog deserves the same care as a normal share. Besides the expiry date there is <strong>Max share
size</strong>, <strong>Max uses</strong> (&ldquo;The maximum number of times this URL can be used.&rdquo;) as well as <strong>Simple
mode</strong> and <strong>Public access</strong>. <code>Max uses</code> defaults to 1 – leave it there if you expect exactly one
delivery. Any higher value (up to 1000) means that, until then, anyone the link is forwarded to can
drop files on your disk. <strong>Public access</strong> is on by default; then anyone with the link to the
resulting share can view it as well. Switch it off and only you and the uploader can see it.</p>
<p>The same applies to link length as above: Pingvin explicitly uses the configured
<code>Default share ID length</code> for reverse shares too. And because other people&rsquo;s files now land on your
server, this is the moment when the ClamAV integration genuinely pays off.</p>
<h2 id="when-it-doesnt-work">When it doesn&rsquo;t work</h2>
<div class="troubleshoot not-prose">
<p><strong>The links you send point to <code>http://localhost:3000</code>.</strong> The <strong>App URL</strong> under Configuration → General is still on its default. Pingvin does not learn its public address by itself – the reverse proxy does not tell it. Enter <code>https://YOUR_DOMAIN</code> and save; links you already sent have to be recreated.</p>
<p><strong>Every visitor shows up with the same IP in the access logs.</strong> That is the address from the Docker proxy network, and <code>TRUST_PROXY: &quot;true&quot;</code> is missing from the Compose file. Only with it does Pingvin evaluate the <code>X-Forwarded-For</code> header. But set it only when a proxy really is in front – without one, any client can invent its own source IP.</p>
<p><strong>A large upload aborts halfway through.</strong> Check <strong>Max size</strong> under Configuration → Share first; the default is 1 GB, and the interface tells you before you even upload (&ldquo;Maximum total upload size: 1 GB.&rdquo;). If that is not it, <strong>Chunk size</strong> is the next lever: pieces smaller than the default 10 MB are more reliable on shaky connections, larger ones faster on stable ones.</p>
<p><strong>Strangers are creating accounts on your instance.</strong> Then <strong>Allow registration</strong> is still on – the shipping state. Switch it off under Configuration → Security &amp; Access and delete the foreign accounts under Administration → User management. To verify: <code>curl -s -o /dev/null -w &quot;%{http_code}\n&quot; https://YOUR_DOMAIN/auth/signUp</code> has to return <code>307</code> afterwards, not <code>200</code>.</p>
<p><strong>You get logged out immediately after signing in.</strong> Usually <strong>Secure cookies</strong> is enabled while you are visiting over <code>http://</code> – the browser then discards the cookie. Open the site over HTTPS. If you deliberately run Pingvin without TLS, the switch has to stay off; the setting says as much itself: &ldquo;If enabled, the site will not function when accessed over HTTP.&rdquo;</p>

</div>
<p class="mt-3 text-sm text-slate-500 dark:text-slate-400">
  Your problem is not listed?
  <a href="/en/errors/" class="font-medium text-paprika-700 hover:underline dark:text-paprika-400">Search all error messages →</a>
</p>

<h2 id="maintenance--backups">Maintenance &amp; backups</h2>
<p><strong>Updates</strong> are a tag change. Read the release notes first, because the fork is actively developed
and versions appear at short intervals:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">cd</span> /opt/pingvin
</span></span><span class="line"><span class="cl">cp -a data data-backup-<span class="k">$(</span>date +%F<span class="k">)</span>
</span></span><span class="line"><span class="cl">sed -i <span class="s1">&#39;s/pingvin-share-x:v2.0.0/pingvin-share-x:vNEW_VERSION/&#39;</span> compose.yaml
</span></span><span class="line"><span class="cl">docker compose pull <span class="o">&amp;&amp;</span> docker compose up -d
</span></span><span class="line"><span class="cl">docker compose logs --tail <span class="m">5</span></span></span></code></pre></div>
</div>
<p>The copy beforehand matters because database migrations run at startup. One update per quarter is
enough in practice, unless something security-relevant is announced.</p>
<p><strong>Two things need backing up</strong>, and both live under <code>data/</code>:</p>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">/opt/pingvin/data/pingvin-share.db     # accounts, shares, settings (SQLite)
</span></span><span class="line"><span class="cl">/opt/pingvin/data/uploads/             # the actual files</span></span></code></pre></div>
</div>
<p>Here a question is worth asking that you would not ask of other services: <strong>do you want to back up
the uploads at all?</strong> The whole point is that files disappear once the share expires. A backup that
holds on to them undoes exactly that – and because they are stored unencrypted, they travel into
your backup target in the clear. For most people it is enough to back up the database and
deliberately leave the uploads out. If you take both, the backup belongs encrypted, the way
<a href="/en/tutorials/restic-backups/">Restic</a> does it anyway.</p>
<p><strong>Cleanup does not happen by itself</strong> if you use &ldquo;Permanent share&rdquo;. Expired shares Pingvin clears
out on its own; permanent ones stay until you delete them. A look at
<code>du -sh /opt/pingvin/data/uploads</code> every few months tells you whether something has piled up.</p>
<p><strong>Realistic effort:</strong> next to nothing once it is set up – one update per quarter and the occasional
glance at disk usage. The one point that needs discipline is how you handle links: every link you
send is an access grant, and unlike a password it cannot be recalled. That is what the expiry date
is for.</p>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-herb-400 bg-herb-50 dark:border-herb-700 dark:bg-herb-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">🧑‍🍳</span>Even more private
  </p>
  <div class="prose-kitchen text-sm">If Pingvin is only there for you and a few acquaintances, it does not have to sit on the open
internet. Behind an IP restriction or entirely inside the VPN, the service is invisible from
outside – the pattern for that is in
<a href="/en/tutorials/internal-services-vpn-only/">making services reachable only over VPN</a>. For shares to
outsiders you will still need the public route, of course.</div>
</div>
]]></content:encoded></item></channel></rss>