Right now clickhouse allows to create TTL columns for Distributed tables during alters. It is working normally until server restart. After that it won't start because engine distributed doesn't support TTL clause. Modify metadata is required.

My cluster has 21.3 but tested it also with 21.11 and the same behavior.
It is blocked during create but it should be also be blocked during alters.
Tested on table from tutorial
ENGINE = Distributed('stage, 'default', 'hits_v1') 
alter table hits_v1_all modify column UTMCampaign String TTL EventDate + INTERVAL 1 MONTH ← it passes normally
PartitionCommandsResultInfo unfreezeAll(
        const String & backup_name,
        ContextPtr context,
        TableLockHolder & table_lock_holder);
    
    bool hasDataToBackup() const override { return true; }
    
    BackupEntries backupData(ContextPtr context, const ASTs & partitions) override;
    static BackupEntries backupDataParts(const DataPartsVector & data_parts);
    
    RestoreTaskPtr restoreDataParts(
        const std::unordered_set<String> & partition_ids,
        const BackupPtr & backup,
        const String & data_path_in_backup,
        SimpleIncrement * increment);
    
    void movePartitionToDisk(const ASTPtr & partition, const String & name, bool moving_part, ContextPtr context);
    
    void movePartitionToVolume(const ASTPtr & partition, const String & name, bool moving_part, ContextPtr context);
    
    
    
    void checkPartitionCanBeDropped(const ASTPtr & partition, ContextPtr local_context);
    void checkPartCanBeDropped(const String & part_name);
    Pipe alterPartition(
        const StorageMetadataPtr & metadata_snapshot,
        const PartitionCommands & commands,
        ContextPtr query_context) override;
    size_t getColumnCompressedSize(const std::string & name) const