Index: backend/src/main/java/com/trekr/backend/controller/CustomTrackingController.java
===================================================================
--- backend/src/main/java/com/trekr/backend/controller/CustomTrackingController.java	(revision e9a0543bbb622a2b343d1f5fe3b2df7ddb6a0bd3)
+++ backend/src/main/java/com/trekr/backend/controller/CustomTrackingController.java	(revision e9a0543bbb622a2b343d1f5fe3b2df7ddb6a0bd3)
@@ -0,0 +1,91 @@
+package com.trekr.backend.controller;
+
+import com.trekr.backend.dto.discipline.CreateCustomTrackingCategoryRequest;
+import com.trekr.backend.dto.discipline.CreateTaskRequest;
+import com.trekr.backend.dto.discipline.CustomTrackingCategoriesResponse;
+import com.trekr.backend.dto.discipline.CustomTrackingCategoryDto;
+import com.trekr.backend.dto.discipline.TaskDto;
+import com.trekr.backend.dto.discipline.TasksResponse;
+import com.trekr.backend.dto.discipline.UpdateTaskFinishedRequest;
+import com.trekr.backend.dto.discipline.UpdateTaskRequest;
+import com.trekr.backend.security.UserPrincipal;
+import com.trekr.backend.service.CustomTrackingService;
+import jakarta.validation.Valid;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.core.Authentication;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("/api/discipline/custom-tracking-categories")
+public class CustomTrackingController {
+
+    private final CustomTrackingService customTrackingService;
+
+    public CustomTrackingController(CustomTrackingService customTrackingService) {
+        this.customTrackingService = customTrackingService;
+    }
+
+    @GetMapping
+    public CustomTrackingCategoriesResponse customTrackingCategories(Authentication authentication) {
+        UserPrincipal principal = (UserPrincipal) authentication.getPrincipal();
+        return customTrackingService.getCustomTrackingCategories(principal.getUserId());
+    }
+
+    @PostMapping
+    @ResponseStatus(HttpStatus.CREATED)
+    public CustomTrackingCategoryDto createCustomTrackingCategory(
+            @Valid @RequestBody CreateCustomTrackingCategoryRequest request,
+            Authentication authentication) {
+        UserPrincipal principal = (UserPrincipal) authentication.getPrincipal();
+        return customTrackingService.createCustomTrackingCategory(principal.getUserId(), request);
+    }
+
+    @GetMapping("/{customTrackingId}/tasks")
+    public TasksResponse tasks(
+            @PathVariable Long customTrackingId,
+            Authentication authentication) {
+        UserPrincipal principal = (UserPrincipal) authentication.getPrincipal();
+        return customTrackingService.getCustomCategoryTasks(principal.getUserId(), customTrackingId);
+    }
+
+    @PostMapping("/{customTrackingId}/tasks")
+    @ResponseStatus(HttpStatus.CREATED)
+    public TaskDto createTask(
+            @PathVariable Long customTrackingId,
+            @Valid @RequestBody CreateTaskRequest request,
+            Authentication authentication) {
+        UserPrincipal principal = (UserPrincipal) authentication.getPrincipal();
+        return customTrackingService.createCustomCategoryTask(principal.getUserId(), customTrackingId, request);
+    }
+
+    @PutMapping("/{customTrackingId}/tasks/{taskId}")
+    public TaskDto updateTask(
+            @PathVariable Long customTrackingId,
+            @PathVariable Long taskId,
+            @Valid @RequestBody UpdateTaskRequest request,
+            Authentication authentication) {
+        UserPrincipal principal = (UserPrincipal) authentication.getPrincipal();
+        return customTrackingService.updateCustomCategoryTask(principal.getUserId(), customTrackingId, taskId, request);
+    }
+
+    @PatchMapping("/{customTrackingId}/tasks/{taskId}/finished")
+    public TaskDto updateTaskFinished(
+            @PathVariable Long customTrackingId,
+            @PathVariable Long taskId,
+            @Valid @RequestBody UpdateTaskFinishedRequest request,
+            Authentication authentication) {
+        UserPrincipal principal = (UserPrincipal) authentication.getPrincipal();
+        return customTrackingService.updateCustomCategoryTaskFinished(principal.getUserId(), customTrackingId, taskId, request);
+    }
+
+    @DeleteMapping("/{customTrackingId}/tasks/{taskId}")
+    @ResponseStatus(HttpStatus.NO_CONTENT)
+    public void deleteTask(
+            @PathVariable Long customTrackingId,
+            @PathVariable Long taskId,
+            Authentication authentication) {
+        UserPrincipal principal = (UserPrincipal) authentication.getPrincipal();
+        customTrackingService.deleteCustomCategoryTask(principal.getUserId(), customTrackingId, taskId);
+    }
+}
+
Index: backend/src/main/java/com/trekr/backend/dto/discipline/CreateCustomTrackingCategoryRequest.java
===================================================================
--- backend/src/main/java/com/trekr/backend/dto/discipline/CreateCustomTrackingCategoryRequest.java	(revision e9a0543bbb622a2b343d1f5fe3b2df7ddb6a0bd3)
+++ backend/src/main/java/com/trekr/backend/dto/discipline/CreateCustomTrackingCategoryRequest.java	(revision e9a0543bbb622a2b343d1f5fe3b2df7ddb6a0bd3)
@@ -0,0 +1,29 @@
+
+package com.trekr.backend.dto.discipline;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
+
+public class CreateCustomTrackingCategoryRequest {
+
+	@NotBlank(message = "Name is required")
+	@Size(max = 100, message = "Name must be at most 100 characters")
+	private String name;
+
+	public CreateCustomTrackingCategoryRequest() {
+	}
+
+	public CreateCustomTrackingCategoryRequest(String name) {
+		this.name = name;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+}
+
+
Index: backend/src/main/java/com/trekr/backend/dto/discipline/CustomTrackingCategoriesResponse.java
===================================================================
--- backend/src/main/java/com/trekr/backend/dto/discipline/CustomTrackingCategoriesResponse.java	(revision e9a0543bbb622a2b343d1f5fe3b2df7ddb6a0bd3)
+++ backend/src/main/java/com/trekr/backend/dto/discipline/CustomTrackingCategoriesResponse.java	(revision e9a0543bbb622a2b343d1f5fe3b2df7ddb6a0bd3)
@@ -0,0 +1,26 @@
+
+package com.trekr.backend.dto.discipline;
+
+import java.util.List;
+
+public class CustomTrackingCategoriesResponse {
+
+	private List<CustomTrackingCategoryDto> items;
+
+	public CustomTrackingCategoriesResponse() {
+	}
+
+	public CustomTrackingCategoriesResponse(List<CustomTrackingCategoryDto> items) {
+		this.items = items;
+	}
+
+	public List<CustomTrackingCategoryDto> getItems() {
+		return items;
+	}
+
+	public void setItems(List<CustomTrackingCategoryDto> items) {
+		this.items = items;
+	}
+}
+
+
Index: backend/src/main/java/com/trekr/backend/dto/discipline/CustomTrackingCategoryDto.java
===================================================================
--- backend/src/main/java/com/trekr/backend/dto/discipline/CustomTrackingCategoryDto.java	(revision e9a0543bbb622a2b343d1f5fe3b2df7ddb6a0bd3)
+++ backend/src/main/java/com/trekr/backend/dto/discipline/CustomTrackingCategoryDto.java	(revision e9a0543bbb622a2b343d1f5fe3b2df7ddb6a0bd3)
@@ -0,0 +1,34 @@
+
+package com.trekr.backend.dto.discipline;
+
+public class CustomTrackingCategoryDto {
+
+	private Long customTrackingId;
+	private String name;
+
+	public CustomTrackingCategoryDto() {
+	}
+
+	public CustomTrackingCategoryDto(Long customTrackingId, String name) {
+		this.customTrackingId = customTrackingId;
+		this.name = name;
+	}
+
+	public Long getCustomTrackingId() {
+		return customTrackingId;
+	}
+
+	public void setCustomTrackingId(Long customTrackingId) {
+		this.customTrackingId = customTrackingId;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+}
+
+
Index: backend/src/main/java/com/trekr/backend/repository/CustomTrackingCategoryRepository.java
===================================================================
--- backend/src/main/java/com/trekr/backend/repository/CustomTrackingCategoryRepository.java	(revision e9a0543bbb622a2b343d1f5fe3b2df7ddb6a0bd3)
+++ backend/src/main/java/com/trekr/backend/repository/CustomTrackingCategoryRepository.java	(revision e9a0543bbb622a2b343d1f5fe3b2df7ddb6a0bd3)
@@ -0,0 +1,16 @@
+
+package com.trekr.backend.repository;
+
+import com.trekr.backend.entity.discipline.CustomTrackingCategory;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.util.List;
+
+public interface CustomTrackingCategoryRepository extends JpaRepository<CustomTrackingCategory, Long> {
+
+	List<CustomTrackingCategory> findByUser_UserIdOrderByCustomTrackingIdDesc(Long userId);
+
+	boolean existsByUser_UserIdAndNameIgnoreCase(Long userId, String name);
+}
+
+
Index: backend/src/main/java/com/trekr/backend/repository/TaskRepository.java
===================================================================
--- backend/src/main/java/com/trekr/backend/repository/TaskRepository.java	(revision 4f85b41e4eaa6d668912791fb3cff6efbe7dc1a4)
+++ backend/src/main/java/com/trekr/backend/repository/TaskRepository.java	(revision e9a0543bbb622a2b343d1f5fe3b2df7ddb6a0bd3)
@@ -23,4 +23,10 @@
     List<Task> findByDisciplineUser_UserIdAndFinishedTrue(Long userId);
 
+    List<Task> findByCustomTrackingCategory_CustomTrackingIdAndCustomTrackingCategory_User_UserIdOrderByTaskIdDesc(
+            Long customTrackingId, Long userId);
+
+    int deleteByTaskIdAndCustomTrackingCategory_CustomTrackingIdAndCustomTrackingCategory_User_UserId(
+            Long taskId, Long customTrackingId, Long userId);
+
     @Modifying
     @Query("update Task t set t.finished = false where t.disciplineUser.userId = :userId")
Index: backend/src/main/java/com/trekr/backend/service/CustomTrackingService.java
===================================================================
--- backend/src/main/java/com/trekr/backend/service/CustomTrackingService.java	(revision e9a0543bbb622a2b343d1f5fe3b2df7ddb6a0bd3)
+++ backend/src/main/java/com/trekr/backend/service/CustomTrackingService.java	(revision e9a0543bbb622a2b343d1f5fe3b2df7ddb6a0bd3)
@@ -0,0 +1,183 @@
+package com.trekr.backend.service;
+
+import com.trekr.backend.dto.discipline.CreateCustomTrackingCategoryRequest;
+import com.trekr.backend.dto.discipline.CreateTaskRequest;
+import com.trekr.backend.dto.discipline.CustomTrackingCategoriesResponse;
+import com.trekr.backend.dto.discipline.CustomTrackingCategoryDto;
+import com.trekr.backend.dto.discipline.TaskDto;
+import com.trekr.backend.dto.discipline.TasksResponse;
+import com.trekr.backend.dto.discipline.UpdateTaskFinishedRequest;
+import com.trekr.backend.dto.discipline.UpdateTaskRequest;
+import com.trekr.backend.entity.User;
+import com.trekr.backend.entity.discipline.CustomTrackingCategory;
+import com.trekr.backend.entity.discipline.Task;
+import com.trekr.backend.repository.CustomTrackingCategoryRepository;
+import com.trekr.backend.repository.TaskRepository;
+import com.trekr.backend.repository.UserRepository;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+
+@Service
+public class CustomTrackingService {
+
+    private final CustomTrackingCategoryRepository customTrackingCategoryRepository;
+    private final TaskRepository taskRepository;
+    private final UserRepository userRepository;
+
+    public CustomTrackingService(
+            CustomTrackingCategoryRepository customTrackingCategoryRepository,
+            TaskRepository taskRepository,
+            UserRepository userRepository) {
+        this.customTrackingCategoryRepository = customTrackingCategoryRepository;
+        this.taskRepository = taskRepository;
+        this.userRepository = userRepository;
+    }
+
+    public CustomTrackingCategoriesResponse getCustomTrackingCategories(Long userId) {
+        List<CustomTrackingCategoryDto> items = customTrackingCategoryRepository
+                .findByUser_UserIdOrderByCustomTrackingIdDesc(userId)
+                .stream()
+                .map(c -> new CustomTrackingCategoryDto(c.getCustomTrackingId(), c.getName()))
+                .toList();
+
+        return new CustomTrackingCategoriesResponse(items);
+    }
+
+    @Transactional
+    public CustomTrackingCategoryDto createCustomTrackingCategory(Long userId, CreateCustomTrackingCategoryRequest request) {
+        String name = request.getName();
+        if (name == null || name.isBlank()) {
+            throw new RuntimeException("Name is required");
+        }
+        String trimmed = name.trim();
+        if (trimmed.length() > 100) {
+            throw new RuntimeException("Name is too long");
+        }
+
+        if (customTrackingCategoryRepository.existsByUser_UserIdAndNameIgnoreCase(userId, trimmed)) {
+            throw new RuntimeException("Custom category with that name already exists");
+        }
+
+        User user = userRepository.findById(userId)
+                .orElseThrow(() -> new RuntimeException("User not found"));
+
+        CustomTrackingCategory category = new CustomTrackingCategory();
+        category.setUser(user);
+        category.setName(trimmed);
+        CustomTrackingCategory saved = customTrackingCategoryRepository.save(category);
+
+        return new CustomTrackingCategoryDto(saved.getCustomTrackingId(), saved.getName());
+    }
+
+    public TasksResponse getCustomCategoryTasks(Long userId, Long customTrackingId) {
+        requireOwnership(userId, customTrackingId);
+
+        List<TaskDto> tasks = taskRepository
+                .findByCustomTrackingCategory_CustomTrackingIdAndCustomTrackingCategory_User_UserIdOrderByTaskIdDesc(
+                        customTrackingId, userId)
+                .stream()
+                .map(t -> new TaskDto(t.getTaskId(), t.getName(), t.isFinished()))
+                .toList();
+
+        return new TasksResponse(tasks, false);
+    }
+
+    @Transactional
+    public TaskDto createCustomCategoryTask(Long userId, Long customTrackingId, CreateTaskRequest request) {
+        CustomTrackingCategory category = requireOwnership(userId, customTrackingId);
+
+        String name = request.getName();
+        if (name == null || name.isBlank()) {
+            throw new RuntimeException("Task name is required");
+        }
+        if (name.length() > 200) {
+            throw new RuntimeException("Task name is too long");
+        }
+
+        Task task = new Task();
+        task.setName(name.trim());
+        task.setFinished(false);
+        task.setDisciplineUser(null);
+        task.setCustomTrackingCategory(category);
+
+        Task saved = taskRepository.save(task);
+        return new TaskDto(saved.getTaskId(), saved.getName(), saved.isFinished());
+    }
+
+    @Transactional
+    public TaskDto updateCustomCategoryTask(Long userId, Long customTrackingId, Long taskId, UpdateTaskRequest request) {
+        Task task = taskRepository.findById(taskId)
+                .orElseThrow(() -> new RuntimeException("Task not found"));
+
+        if (task.getCustomTrackingCategory() == null
+                || !customTrackingId.equals(task.getCustomTrackingCategory().getCustomTrackingId())
+                || task.getCustomTrackingCategory().getUser() == null
+                || !userId.equals(task.getCustomTrackingCategory().getUser().getUserId())) {
+            throw new RuntimeException("Task not found");
+        }
+
+        String name = request.getName();
+        if (name == null || name.isBlank()) {
+            throw new RuntimeException("Task name is required");
+        }
+
+        task.setName(name.trim());
+        Task saved = taskRepository.save(task);
+        return new TaskDto(saved.getTaskId(), saved.getName(), saved.isFinished());
+    }
+
+    @Transactional
+    public TaskDto updateCustomCategoryTaskFinished(Long userId, Long customTrackingId, Long taskId,
+                                                    UpdateTaskFinishedRequest request) {
+        Task task = taskRepository.findById(taskId)
+                .orElseThrow(() -> new RuntimeException("Task not found"));
+
+        if (task.getCustomTrackingCategory() == null
+                || !customTrackingId.equals(task.getCustomTrackingCategory().getCustomTrackingId())
+                || task.getCustomTrackingCategory().getUser() == null
+                || !userId.equals(task.getCustomTrackingCategory().getUser().getUserId())) {
+            throw new RuntimeException("Task not found");
+        }
+
+        Boolean finished = request.getIsFinished();
+        if (finished == null) {
+            throw new RuntimeException("isFinished is required");
+        }
+
+        task.setFinished(finished);
+        Task saved = taskRepository.save(task);
+        return new TaskDto(saved.getTaskId(), saved.getName(), saved.isFinished());
+    }
+
+    @Transactional
+    public void deleteCustomCategoryTask(Long userId, Long customTrackingId, Long taskId) {
+        if (taskId == null || customTrackingId == null) {
+            throw new RuntimeException("Task not found");
+        }
+
+        int deleted = taskRepository
+                .deleteByTaskIdAndCustomTrackingCategory_CustomTrackingIdAndCustomTrackingCategory_User_UserId(
+                        taskId, customTrackingId, userId);
+        if (deleted == 0) {
+            throw new RuntimeException("Task not found");
+        }
+    }
+
+    private CustomTrackingCategory requireOwnership(Long userId, Long customTrackingId) {
+        if (customTrackingId == null) {
+            throw new RuntimeException("Custom tracking category not found");
+        }
+
+        CustomTrackingCategory category = customTrackingCategoryRepository.findById(customTrackingId)
+                .orElseThrow(() -> new RuntimeException("Custom tracking category not found"));
+
+        if (category.getUser() == null || !userId.equals(category.getUser().getUserId())) {
+            throw new RuntimeException("Custom tracking category not found");
+        }
+
+        return category;
+    }
+}
+
Index: backend/src/main/java/com/trekr/backend/service/DisciplineService.java
===================================================================
--- backend/src/main/java/com/trekr/backend/service/DisciplineService.java	(revision 4f85b41e4eaa6d668912791fb3cff6efbe7dc1a4)
+++ backend/src/main/java/com/trekr/backend/service/DisciplineService.java	(revision e9a0543bbb622a2b343d1f5fe3b2df7ddb6a0bd3)
@@ -52,4 +52,5 @@
     }
 
+
     public boolean isTracking(Long userId) {
         return disciplineUserRepository.existsById(userId);
Index: backend/src/main/resources/application.properties
===================================================================
--- backend/src/main/resources/application.properties	(revision 4f85b41e4eaa6d668912791fb3cff6efbe7dc1a4)
+++ backend/src/main/resources/application.properties	(revision e9a0543bbb622a2b343d1f5fe3b2df7ddb6a0bd3)
@@ -42,6 +42,8 @@
 # ============================
 spring.datasource.hikari.pool-name=trekrHikariPool
-spring.datasource.hikari.maximum-pool-size=20
-spring.datasource.hikari.minimum-idle=5
+# Remote PostgreSQL (especially via SSH tunnel) often has a small max_connections.
+# Use conservative defaults to avoid exhausting connection slots; override via env if needed.
+spring.datasource.hikari.maximum-pool-size=${SPRING_DATASOURCE_HIKARI_MAX_POOL_SIZE:4}
+spring.datasource.hikari.minimum-idle=${SPRING_DATASOURCE_HIKARI_MIN_IDLE:0}
 spring.datasource.hikari.connection-timeout=30000
 spring.datasource.hikari.idle-timeout=600000
