diff --git a/apps/ssi-abstraction/src/agent/agent.service.ts b/apps/ssi-abstraction/src/agent/agent.service.ts
index 822cd804f7ca6bec4f41a8861b57f116d753567a..00423dca4b2dbff94b294ae6b917b74205934901 100644
--- a/apps/ssi-abstraction/src/agent/agent.service.ts
+++ b/apps/ssi-abstraction/src/agent/agent.service.ts
@@ -51,7 +51,7 @@ import { parseDid } from '../common/utils.js';
 import { LEDGERS } from '../config/ledger.js';
 
 import { AgentLogger } from './logger.js';
-import { TailsFileService } from './revocation/TailsFileService.js';
+import { S3TailsFileService } from './revocation/TailsFileService.js';
 
 export type TenantAgent = Agent<Omit<AgentService['modules'], 'tenants'>>;
 export type AppAgent = Agent<AgentService['modules']>;
@@ -102,7 +102,14 @@ export class AgentService implements OnApplicationShutdown {
     const { autoAcceptConnection, autoAcceptCredential, autoAcceptProof } =
       this.configService.get('agent');
 
-    const tailsServerBaseUrl = this.configService.get('tailsServerBaseUrl');
+    const tailsServerBaseUrl = this.configService.getOrThrow(
+      'tailsServer.baseUrl',
+    );
+    const tailsServerBucketName = this.configService.getOrThrow(
+      'tailsServer.bucketName',
+    );
+    const s3Secret = this.configService.getOrThrow('s3.secret');
+    const s3AccessKey = this.configService.getOrThrow('s3.accessKey');
 
     return {
       connections: new ConnectionsModule({
@@ -138,7 +145,12 @@ export class AgentService implements OnApplicationShutdown {
       anoncreds: new AnonCredsModule({
         anoncreds,
         registries: [new IndyVdrAnonCredsRegistry()],
-        tailsFileService: new TailsFileService({ tailsServerBaseUrl }),
+        tailsFileService: new S3TailsFileService({
+          tailsServerBaseUrl,
+          s3AccessKey,
+          s3Secret,
+          tailsServerBucketName,
+        }),
       }),
       indyVdr: new IndyVdrModule({ indyVdr, networks: this.ledgers }),
 
diff --git a/apps/ssi-abstraction/src/agent/revocation/TailsFileService.ts b/apps/ssi-abstraction/src/agent/revocation/TailsFileService.ts
index 81401315b4a13733fc3252544a2145dbcdf9d479..9938242e0ca4c19d0098762a6f03c14f7bd92ce1 100644
--- a/apps/ssi-abstraction/src/agent/revocation/TailsFileService.ts
+++ b/apps/ssi-abstraction/src/agent/revocation/TailsFileService.ts
@@ -5,15 +5,72 @@ import { BasicTailsFileService } from '@credo-ts/anoncreds';
 import FormData from 'form-data';
 import fs from 'fs';
 
-export class TailsFileService extends BasicTailsFileService {
-  private tailsServerBaseUrl?: string;
+export type UploadToS3Options = {
+  s3Url: string;
+  bucketName: string;
+  fileId: string;
+  content: Uint8Array;
+  accessKey: string;
+  secret: string;
+};
 
-  public constructor(options?: {
+// Upload to S3 and return the URL to fetch it from
+export const uploadToS3 = async ({
+  s3Url,
+  content,
+  fileId,
+  bucketName,
+  accessKey,
+  secret,
+}: UploadToS3Options) => {
+  // TODO: double check all headers
+  const headers = new Headers();
+  headers.set('Host', s3Url);
+  headers.set('Date', generateRfc1123Date());
+  headers.set('Content-Type', 'application/octet-stream');
+  headers.set('Authorization', accessKey);
+  headers.set('Secret', secret);
+
+  const sanitizedUrl = s3Url.endsWith('/')
+    ? s3Url.slice(0, s3Url.length - 1)
+    : s3Url;
+
+  const url = `${sanitizedUrl}/${bucketName}/${fileId}`;
+
+  // TODO: check whether we need to include the sig or not
+  const result = await axios.put(url, content, {
+    headers,
+  });
+
+  if (result.status > 299) {
+    throw new Error(`Error uploading to S3. Error: ${JSON.stringify(result)}`);
+  }
+
+  return url;
+};
+
+export class S3TailsFileService extends BasicTailsFileService {
+  private tailsServerBaseUrl: string;
+  private tailsServerBucketName: string;
+  private s3Secrets: {
+    s3AccessKey: string;
+    s3Secret: string;
+  };
+
+  public constructor(options: {
     tailsDirectoryPath?: string;
-    tailsServerBaseUrl?: string;
+    tailsServerBaseUrl: string;
+    tailsServerBucketName: string;
+    s3AccessKey: string;
+    s3Secret: string;
   }) {
     super(options);
-    this.tailsServerBaseUrl = options?.tailsServerBaseUrl;
+    this.tailsServerBaseUrl = options.tailsServerBaseUrl;
+    this.tailsServerBucketName = options.tailsServerBucketName;
+    this.s3Secrets = {
+      s3AccessKey: options.s3AccessKey,
+      s3Secret: options.s3Secret,
+    };
   }
 
   public async uploadTailsFile(
@@ -22,6 +79,11 @@ export class TailsFileService extends BasicTailsFileService {
       revocationRegistryDefinition: AnonCredsRevocationRegistryDefinition;
     },
   ) {
+    const headers = this.prepareS3Headers(
+      this.tailsServerBaseUrl,
+      this.s3Secrets.s3AccessKey,
+      this.s3Secrets.s3Secret,
+    );
     const revocationRegistryDefinition = options.revocationRegistryDefinition;
     const localTailsFilePath = revocationRegistryDefinition.value.tailsLocation;
     const pathParts = localTailsFilePath.split('/');
@@ -31,11 +93,14 @@ export class TailsFileService extends BasicTailsFileService {
     const readStream = fs.createReadStream(localTailsFilePath);
     data.append('file', readStream);
 
+    const tailsFileUrl = `${this.tailsServerBaseUrl}/${this.tailsServerBucketName}/${tailsFileId}`;
+
     const response = await agentContext.config.agentDependencies.fetch(
-      `${this.tailsServerBaseUrl}/${tailsFileId}`,
+      tailsFileUrl,
       {
         method: 'PUT',
         body: data,
+        headers,
       },
     );
 
@@ -44,7 +109,34 @@ export class TailsFileService extends BasicTailsFileService {
     }
 
     return {
-      tailsFileUrl: `${this.tailsServerBaseUrl}/${encodeURIComponent(tailsFileId)}`,
+      tailsFileUrl,
     };
   }
+
+  private prepareS3Headers(url: string, accessKey: string, secret: string) {
+    const rfc1123Date =
+      new Date()
+        .toLocaleString('en-GB', {
+          timeZone: 'UTC',
+          hour12: false,
+          weekday: 'short',
+          year: 'numeric',
+          month: 'short',
+          day: '2-digit',
+          hour: '2-digit',
+          minute: '2-digit',
+          second: '2-digit',
+        })
+        .replace(/(?:(\d),)/, '$1') + ' GMT';
+
+    // TODO: double check all headers
+    const headers = new Headers();
+    headers.set('Host', url);
+    headers.set('Date', rfc1123Date);
+    headers.set('Content-Type', 'application/octet-stream');
+    headers.set('Authorization', accessKey);
+    headers.set('Secret', secret);
+
+    return headers;
+  }
 }
diff --git a/apps/ssi-abstraction/src/config/__tests__/mockConfig.ts b/apps/ssi-abstraction/src/config/__tests__/mockConfig.ts
index 29af564530a0c00a3f1fa304791980b45b614a2a..fdb409391071acaee0554ee8ff53505cf589359c 100644
--- a/apps/ssi-abstraction/src/config/__tests__/mockConfig.ts
+++ b/apps/ssi-abstraction/src/config/__tests__/mockConfig.ts
@@ -8,7 +8,14 @@ import { validationSchema } from '../validation.js';
 const mockConfig = (port: number = 3001, withLedger = false): AppConfig => ({
   agentHost: '',
   port: 3000,
-  tailsServerBaseUrl: 'http://localhost:8080',
+  s3: {
+    secret: 'some-secret',
+    accessKey: 'some-access-key',
+  },
+  tailsServer: {
+    baseUrl: 'http://localhost:8080',
+    bucketName: 'tails',
+  },
   jwtSecret: '',
   nats: {
     url: 'localhost',
diff --git a/apps/ssi-abstraction/src/config/config.ts b/apps/ssi-abstraction/src/config/config.ts
index 85ff0a2c9606236d69dca31f49c05bba1c0bd582..b5920024d79fe58af1a021fc6220d3aff397b43c 100644
--- a/apps/ssi-abstraction/src/config/config.ts
+++ b/apps/ssi-abstraction/src/config/config.ts
@@ -4,7 +4,16 @@ export interface AppConfig {
   agentHost: string;
   port: number;
   jwtSecret: string;
-  tailsServerBaseUrl: string;
+
+  tailsServer: {
+    baseUrl: string;
+    bucketName: string;
+  };
+
+  s3: {
+    secret: string;
+    accessKey: string;
+  };
 
   nats: {
     url: string;
@@ -30,7 +39,6 @@ export const config = (): AppConfig => ({
   agentHost: process.env.AGENT_HOST || '',
   port: parseInt(process.env.PORT || '3000'),
   jwtSecret: process.env.JWT_SECRET || '',
-  tailsServerBaseUrl: process.env.TAILS_SERVER_BASE_URL || '',
 
   nats: {
     url: process.env.NATS_URL || '',
@@ -38,6 +46,16 @@ export const config = (): AppConfig => ({
     password: process.env.NATS_PASSWORD || '',
   },
 
+  s3: {
+    secret: process.env.S3_SECRET || '',
+    accessKey: process.env.S3_ACCESS_KEY || '',
+  },
+
+  tailsServer: {
+    baseUrl: process.env.TAILS_SERVER_BASE_URL || '',
+    bucketName: process.env.TAILS_SERVER_BUCKET_NAME || '',
+  },
+
   agent: {
     name: process.env.AGENT_NAME || '',
     walletId: process.env.AGENT_WALLET_ID || '',
diff --git a/apps/ssi-abstraction/src/config/validation.ts b/apps/ssi-abstraction/src/config/validation.ts
index 92df765fa0c75bf87b6f17d5e0ae306232aec2d1..68cd08234665f7da9bdf7f107f33cb6c94e48ad1 100644
--- a/apps/ssi-abstraction/src/config/validation.ts
+++ b/apps/ssi-abstraction/src/config/validation.ts
@@ -4,8 +4,14 @@ export const validationSchema = Joi.object({
   NATS_URL: Joi.string().required(),
   NATS_USER: Joi.string().optional(),
   NATS_PASSWORD: Joi.string().optional(),
+
   PORT: Joi.number().required(),
+
   TAILS_SERVER_BASE_URL: Joi.string().required(),
+  TAILS_SERVER_BUCKET_NAME: Joi.string().required(),
+
+  S3_SECRET: Joi.string().required(),
+  S3_ACCESS_KEY: Joi.string().required(),
 
   AGENT_NAME: Joi.string().required(),
   AGENT_WALLET_ID: Joi.string().required(),