diff --git a/server/src/services/system-config.service.spec.ts b/server/src/services/system-config.service.spec.ts
index f81abc4795d40..ffbb695e125aa 100644
--- a/server/src/services/system-config.service.spec.ts
+++ b/server/src/services/system-config.service.spec.ts
@@ -237,6 +237,24 @@ describe(SystemConfigService.name, () => {
       expect(systemMock.readFile).toHaveBeenCalledWith('immich-config.json');
     });
 
+    it('should transform booleans', async () => {
+      configMock.getEnv.mockReturnValue(mockEnvData({ configFile: 'immich-config.json' }));
+      systemMock.readFile.mockResolvedValue(JSON.stringify({ ffmpeg: { twoPass: 'false' } }));
+
+      await expect(sut.getSystemConfig()).resolves.toMatchObject({
+        ffmpeg: expect.objectContaining({ twoPass: false }),
+      });
+    });
+
+    it('should transform numbers', async () => {
+      configMock.getEnv.mockReturnValue(mockEnvData({ configFile: 'immich-config.json' }));
+      systemMock.readFile.mockResolvedValue(JSON.stringify({ ffmpeg: { threads: '42' } }));
+
+      await expect(sut.getSystemConfig()).resolves.toMatchObject({
+        ffmpeg: expect.objectContaining({ threads: 42 }),
+      });
+    });
+
     it('should log errors with the config file', async () => {
       configMock.getEnv.mockReturnValue(mockEnvData({ configFile: 'immich-config.json' }));
 
diff --git a/server/src/utils/config.ts b/server/src/utils/config.ts
index 3a6e079e873f8..ce8a2da83985d 100644
--- a/server/src/utils/config.ts
+++ b/server/src/utils/config.ts
@@ -1,5 +1,5 @@
 import AsyncLock from 'async-lock';
-import { plainToInstance } from 'class-transformer';
+import { instanceToPlain, plainToInstance } from 'class-transformer';
 import { validate } from 'class-validator';
 import { load as loadYaml } from 'js-yaml';
 import * as _ from 'lodash';
@@ -87,13 +87,13 @@ const buildConfig = async (repos: RepoDeps) => {
     : await metadataRepo.get(SystemMetadataKey.SYSTEM_CONFIG);
 
   // merge with defaults
-  const config = _.cloneDeep(defaults);
+  const rawConfig = _.cloneDeep(defaults);
   for (const property of getKeysDeep(partial)) {
-    _.set(config, property, _.get(partial, property));
+    _.set(rawConfig, property, _.get(partial, property));
   }
 
   // check for extra properties
-  const unknownKeys = _.cloneDeep(config);
+  const unknownKeys = _.cloneDeep(rawConfig);
   for (const property of getKeysDeep(defaults)) {
     unsetDeep(unknownKeys, property);
   }
@@ -103,7 +103,8 @@ const buildConfig = async (repos: RepoDeps) => {
   }
 
   // validate full config
-  const errors = await validate(plainToInstance(SystemConfigDto, config));
+  const instance = plainToInstance(SystemConfigDto, rawConfig);
+  const errors = await validate(instance);
   if (errors.length > 0) {
     if (configFile) {
       throw new Error(`Invalid value(s) in file: ${errors}`);
@@ -112,6 +113,9 @@ const buildConfig = async (repos: RepoDeps) => {
     }
   }
 
+  // return config with class-transform changes
+  const config = instanceToPlain(instance) as SystemConfig;
+
   if (config.server.externalDomain.length > 0) {
     config.server.externalDomain = new URL(config.server.externalDomain).origin;
   }